Use The Source, Luke
Source Server Helps You Kill Bugs Dead In Visual Studio 2005
This article discusses:
|This article uses the following technologies:
Visual Studio 2005, Visual SourceSafe
Code download available at:Bugslayer2006_08.exe(181 KB)
Working with Source Server
What You'll Need
A Simple Scenario
Getting Started with Indexing
Working with Subprojects
Debugging with WinDBG
Debugging in Visual Studio 2005
Better and Easier Indexing
When you look at a minidump or live debugging session while trying to rout out an elusive bug, you need the right contributors to those bits. Access to the correct symbols and source code is critical, whether you're doing Microsoft® .NET or native C++ development. Without them, you'll be debugging at the assembly language level. Sure, if you're paid by the hour, this can be a fantastic way to earn yourself a new car! But there are better ways to spend your time.
In past columns (msdn.microsoft.com/msdnmag/issues/02/06/Bugslayer), I've discussed what it takes to get the symbols you need into the debugger. Obviously, you'll need a symbol server. I can't stress enough how important this is. If you don't already have one set up in your development shop, get one now.
Once you have access to the right symbols set up, you need to get the right source. If your team has been vigilant about version control, you'll know exactly which version of the source code is in a particular binary. But wouldn't it be fantastic if the debugger would automatically grab the exact source code as it's loading the exact PDB file? Then you wouldn't have to guess and debugging would be much simpler.
In this article, I'll show how you can always get your source code with Source Server. Because the latest releases of WinDBG and Visual Studio® know exactly how to use Source Server, its benefits are available to both .NET and native C++ developers.
Working with Source Server
A symbol server is a database that uses the file system. When you load FOO.PDB, the debugger builds a directory path that starts with your cache directory, appends the file to open, and finishes with the GUID uniquely identifying the version of the file. In that directory path is the actual PDB file the debugger needs (see Figure 1).
Figure 1** Building the Path **
The source server doesn't store source code as the symbol server does with PDBs. A PDB file created with any of the recent compilers has in it the full path for each of the source files (compilands) that were used to create the binary. When you run Source Server on your binaries, it adds a section to the PDB file. Here it embeds the exact version of the file reported by the version control system and the actual commands used to retrieve the file from the version control system. This process is referred to as indexing the source code.
Once you've indexed the source code, and assuming you have access to the version control system, the debuggers will ask the version control system for the correct version of the file. I'll show you how you can see all of this information in the PDB files a bit later in this article.
Source Server must be told how to use your version control system. With the latest source server binaries, you get support for Perforce, Subversion, Concurrent Versions System (CVS), and Microsoft Visual SourceSafe®. Sadly, there is no support for the new Visual Studio Team Foundation System version control system out of the box. If you'd like to add support for Visual Studio Team Foundation System or a different version control product, the source server tools are extensible. As long as the version control system you use supports a command-line tool for getting version information and doing pulls, you can write a module to operate with your system. The SRCSRV.DOC file in the Source Server installation directory explains how you can create and integrate your own module.
The Source Server workflow is straightforward. First, make the debug and release builds of your app. After you've run your automated smoke tests to ensure you have a good build, use the Source Server programs to index the PDB files with version control information. The final step is to store those indexed PDB files in your symbol server. Figure 2 illustrates the process.
Figure 2** Source Server Workflow **
What You'll Need
Let's take a look at what you'll need to get your system up and running. As an example, I'll tell you about the configuration I used at the time of writing this article.
I used Visual SourceSafe for version control as it is the most common version control system for Windows® development, but the concepts I discuss here apply to most common version control solutions. For debugging I used WinDBG 184.108.40.206 for both x86 and x64. Versions back to 220.127.116.11 will work with Source Server.
Only Visual Studio 2005 Professional and Team Editions know about Source Server. If you're a native C++ developer using Visual Studio .NET 2003 or Visual C++® 6.0, you can use Visual Studio 2005 to debug your binaries. (But be aware that once you see the symbol and source server support in Visual Studio 2005, and a slew of other enhancements, you'll be thinking seriously about upgrading permanently.)
The Source Server binaries, which do the actual indexing, are part of the Debugging Tools for Windows package, which you can download for free from the Microsoft Web site. If you will be running on a 64-bit system, you can use the 64-bit versions, but the 32-bit versions also work fine on x64. Visual Studio 2005 comes with a version of SRCSRV.DLL older than the one with the 18.104.22.168 version of the Debugging Tools for Windows, which fixes some bugs in the Visual SourceSafe usage. You will want to copy the x86 version of SRCSRV.DLL from the Debugging Tools for Windows directory to Visual Studio .NET Installation Directory\Common7\IDE and overwrite the old version.
The necessary Source Server binaries for doing the source indexing are not installed by default. You'll need to custom-install the SDK portion of the Debugging Tools for Windows. This will add an SDK directory under the Debugging Tools for Windows directory. Inside this directory, you'll find a SRCSRV directory, which contains all the goodies. You can leave it and point to it or copy it somewhere else, but the directory with the tools has to be in your path.
After installing the Debugging Tools for Windows, you'll need to install Perl. I use Perl 5.8.7 from www.cpan.org/src/5.0. I built my binaries from the sources with Visual Studio .NET 2003, as the source code does not yet build with the Visual Studio 2005 compiler.
Notice in the Source Server directory that most of the heavy lifting is done in Perl. The SSINDEX.CMD file is just a batch file that calls PERL.EXE and passes the Perl code in through standard input. The .PM files are the Perl modules that can talk to the version control systems; the SSINDEX.CMD loads these modules and calls through a standard interface to isolate the system differences. The other CMD files, such as VSSINDEX.CMD, are wrappers for working with specific version control systems, as SSINDEX.CMD needs the version control system on the command line. The PDBSTR.EXE and SRCTOOL.EXE files in the SRCSRV directory read and write to PDB files. The VSSDUMP.EXE program is a helper for the Visual SourceSafe integration; it's only installed with the x86 version of the Debugging Tools for Windows package.
When you run SSINDEX.CMD, after it validates parameters it asks the version control system for the list of all source files (and their version numbers) in the target project and below. PDB files are then searched for recursively. For each PDB file found, the Perl code calls SRCTOOL.EXE to extract the list of source files. The code looks through the source files in the PDB file and sees if they match any of the files stored earlier from the version control search: if a match is found, the code saves that file info. Once the code has looked at all the source files in the PDB file, it's time to call PDBSTR.EXE to write the index stream, called SRCSRV, to the PDB file. When debugging, the debugger looks for this stream. If it is found, the debugger knows there's a source server involved and calls into SRCSRV.DLL to execute the version control system to ensure the right file is accessed.
That's an overview of indexing source code. For more on working with SSINDEX.CMD and its friends, look at the code or SRCSRV.DOC. And remember, if your version control system is not supported, you'll need to write the Perl module that conforms to the interface specified in SRCSRV.DOC.
A Simple Scenario
Now let's put Source Server to work. I'll discuss a simple source tree that comprises two console applications, CppApp and NetApp. CppApp is a native C++ application and NetApp is written in C#. The projects reside in C:\Code\CppApp and C:\Code\NetApp. All output is built to C:\Code\Debug. The Visual SourceSafe version control is on the share \\Critias\BugslayerSource, with CppApp and NetApp as projects of $/ (which is the root in Visual SourceSafe). The $/ project is set to using C:\Code as its working directory, making the $/CppApp and $/NetApp working directories C:\Code\CppApp and C:\Code\NetApp, respectively. Note that to index source files with Visual SourceSafe you are required to set the working directories on the project. I've also applied the version control label "First Version" to the project. Finally, I built both projects. Now they're ready for indexing.
First I need to ensure that the Visual SourceSafe program, SS.EXE, Perl, and the Source Server programs are all in the path for my command shell. If the version control program is not in the path, the source file fetch will silently fail and you won't know why you're looking at the Disassembly window.
Be aware that while the Source Server help says you can specify the server to use on the command line, that doesn't actually work with Visual SourceSafe. You need to set the SSDIR environment variable to the Visual SourceSafe database. In my example I issue the following in the command prompt:
Another problem with Visual SourceSafe is the concept of current project, which is the project selected when you end the SourceSafe Explorer GUI. The source indexing for Visual SourceSafe "respects" the current project, which means it assumes that the current project is the one you want to work on. If you've got a single project in your version control system, that's fine, but no one actually does. In order to get the Visual SourceSafe indexing to work, you'll need to set the Visual SourceSafe current project to the project you are about to index. For indexing at the command line, you can set it with the SS.EXE cp option. In my example, I'd run the following command because I'm working with the root project:
ss cp $/
Next, in C:\Code, I create a new SRCSRV.INI for indexing by defining a logical name for the server and the server path. The example SRCSRV.INI in the Debugging Tools for Windows\SDK\SRCSRV directory has more info on this. In the case of my Visual SourceSafe database, the SRCSRV.INI looks like this:
(With Visual SourceSafe, the value to the right of the equal sign must exactly match the value in the SSDIR environment variable—even the trailing backslash.)
In almost all instances, you'll only need the SRCSRV.INI file during indexing. It will store the BSSRC logical name and location for the server in the processed PDB files. If the machine CRITIAS dies and you need to move the version control, you can put a SRCSRV.INI in the same directory as the debugger's SRCSRV.DLL and the settings in that file will override the indexing embedded in the PDB files. Thus, you wouldn't need to re-index the PDB files.
You can also use the SRCSRV.INI file during debugging to point the debugger to a mirrored version control system that provides read-only access. It's even acceptable to include all your company's version control systems in the SRCSRV.INI under the [variables] section.
SRCSRV.INI can reside in several places. If you've copied the source server code directory to its own spot on the machine, you can put your version there and the indexing tools will pick it up automatically. Or you can put it in the current directory where you execute the indexing commands. You can also tell the tools where to find SRCSRV.INI with a command-line option or set the SRCSRV_INI environment variable to point to it. In my example, I put SRCSRV.INI in the root directory for my source code (C:\Code) and run all my indexing commands from there.
Getting Started with Indexing
To begin indexing, many of you will set command-line options to tell the tools where to find sources, symbols, and the like. Since I'm using Visual SourceSafe, I run VSSINDEX.CMD to do my indexing. I could also use SSINDEX.CMD /System=VSS. There are two help options for the command-line options, -? and -??, where more question marks show more help. This help provides all sorts of switches and environment variables.
There are four command line options of real importance for all version control systems and a fifth necessary for Visual SourceSafe. The first is /Ini, which specifies the SRCSRV.INI file to use. As I mentioned, the file will be automatically found if it's placed in certain locations. But if you want to be specific, use /Ini to set the location manually.
The /Source switch tells the indexing tools where the source code root starts. The default is the current directory. The indexing script will attempt to correlate any source file found in the directory specified and below with files in the version control system.
The /Symbols switch indicates the root directory that will be recursed when looking for PDB files. If a PDB file contains one or more source code files from the version control system, then the PDB file is indexed with the SRCSRV stream, which I'll show later.
For Visual SourceSafe only, the /Label command is required. Because of limitations in Visual SourceSafe, the source indexing cannot figure out the version of a source file in a given directory. Thus, you need to first set a label on the root of the project with Visual SourceSafe and pass that same name to the source indexing tools. As you should always be labeling your builds for good version control hygiene, that should not be too onerous.
The last command-line option, /debug, is the most important. There is a major drawback of the source indexing tools: if any problems are encountered and no PDB files are indexed, there is no notification that anything went wrong. Everything looks to be in tiptop shape until you start debugging and nothing works. This is where /debug comes in. Always run the indexing commands with /debug so you can see what worked and what didn't.
Now let's do some indexing. I have a command shell open and my current directory is C:\Code. Since that's the root of both my source code and symbols, I don't have to set those command-line options. My SRCSRV.INI file is also in that directory, so there's no need to set that command-line value either. I've applied the label "First Version" in SourceSafe already. Finally, I've set the SSDIR environment variable to specify the server. Executing VSSINDEX /Label="First Version" /debug produces the output shown in Figure 3.
Figure 3 Initial Source Indexing Output
[C:\Code]vssindex /Label="First Version" /debug -------------------------------------------------------------------------------- SSIndex.cmd [STATUS] : Server ini file: C:\Code\srcsrv.ini SSIndex.cmd [STATUS] : Source root : C:\Code SSIndex.cmd [STATUS] : Symbols root : C:\Code SSIndex.cmd [STATUS] : Control system : VSS SSIndex.cmd [STATUS] : VSS Server : \\Critias\BugslayerSource SSIndex.cmd [STATUS] : VSS Client Root: C:\Code SSIndex.cmd [STATUS] : VSS Project : $/ SSIndex.cmd [STATUS] : VSS Label : First Version -------------------------------------------------------------------------------- SSIndex.cmd [STATUS] : Running... this will take some time... SSIndex.cmd [STATUS] : Processing vssdump.exe output ... SSIndex.cmd [INFO ] : ... indexing C:\Code\CppApp\Debug\vc80.pdb SSIndex.cmd [INFO ] : ... zero source files found ... SSIndex.cmd [INFO ] : ... indexing C:\Code\debug\CppApp.pdb SSIndex.cmd [INFO ] : ... wrote C:\DOCUME~1\John\LOCALS~1\Temp \indexAEBF.stream to C:\Code\debug\CppApp.pdb ... SSIndex.cmd [INFO ] : ... indexing C:\Code\debug\NetApp.pdb SSIndex.cmd [INFO ] : ... wrote C:\DOCUME~1\John\LOCALS~1\Temp \index14DA6.stream to C:\Code\debug\NetApp.pdb ... SSIndex.cmd [INFO ] : ... indexing C:\Code\NetApp\obj\Debug\NetApp.pdb SSIndex.cmd [INFO ] : ... wrote C:\DOCUME~1\John\LOCALS~1\Temp \indexD8BE.stream to C:\Code\NetApp\obj\Debug\NetApp.pdb ...
As you can see, VSSINDEX.CMD found some PDB files and indexed them. Technically, this all worked. But if you scan down to the line containing vc80.pdb, you'll see that the next line says zero source files found. That's normal for any VC?0.PDB files since those are disposable files. If, however, one of your necessary PDB files ever reports zero source files found, that's a serious problem. However, the indexing tools don't stop on indexing errors. That means you must specify the /debug option and carefully check that your PDB files are all properly indexed.
The output in Figure 3 shows another problem that can slow down indexing performance. I left the /Symbols switch set to the default, which treats the current directory as the root for all PDB files. The last PDB file indexed with the file C:\Code\NetApp\obj\Debug\NetApp.pdb, which is where .NET applications are built and then copied to the output directory you specify. If you index these files, the indexing time will double.
It's best to build applications to a central directory or directory tree for easier maintenance. In my example, everything builds to the C:\Code\Debug directory. Therefore, when I use the command line VSSINDEX /Label= "First Version" /symbols=.\Debug /debug, VSSINDEX only indexes the PDB files I will put in my symbol server.
Note that if all your source directories are directly off the drive root directory, source indexing will not work. The first time I used the Source Server tools, my setup looked like this and I was unable to index any of my PDB files. In my current example, I've purposely put my source code in the C:\Code directory to work around this problem. If all your source directories are off the root, you'll need to change your build system to put the source code at least one level down.
Working with Subprojects
The next surprise will be if the project you want to index is a subproject in your version control system. For example, imagine that my sample has one root project, $/Happy, with two subprojects, $/Happy/Foo and $Happy/Bar. Under these are all the subprojects necessary for those pieces of code. Now imagine that I am only responsible for source indexing $/Happy/Foo and the working directory is D:\Dev\Happy\Foo. If I run the indexing command vssindex /debug /Symbols=.\Debug, I'll find that zero source code files were indexed.
Why? I made a bad assumption—that the source indexing commands consider the current directory and match the version control projects appropriately—but they don't. The default project for source indexing is the root of the version control system. (Remember that all my testing is with Visual SourceSafe. For other version control systems, you'll need to do some testing.)
I need to use the command-line vssindex /debug /Symbols=.\Debug /Project=Happy/Foo. The Visual SourceSafe source indexing code puts in the leading $/ necessary to SS.EXE. It's a good idea to always specify the /Project command line option, even if it is the root. (Again, you need to manually test various scenarios to see what works in your environment.)
Seeing that a .STREAM file was written to the PDB file is a good sign. To verify the stream, I use PDBSTR.EXE to look inside it. PDBSTR.EXE is a little persnickety, so I have to get the command-line options just right so that the output looks like no stream is in the PDB. The first parameter must be –r (read the PDB). The second parameter is –p:file.PDB (the PDB to view). And the last parameter is the stream that I want to see: this is always –s:srcsrv. Figure 4 shows the output that I get from running PDBSTR.EXE on CPPAPP.PDB.
Figure 4 PDBSTR.EXE Output
[C:\Code\debug]pdbstr -r -p:CppApp.pdb -s:srcsrv SRCSRV: ini ------------------------------------------------ VERSION=1 INDEXVERSION=2 VERCTRL=Visual Source Safe DATETIME=Fri May 19 00:29:22 2006 SRCSRV: variables ------------------------------------------ SSDIR=\\Critias\BugslayerSource SRCSRVENV=SSDIR=%BSSRC% VSSTRGDIR=%targ%\%var2%\%fnbksl%(%var3%)\%var4% VSS_EXTRACT_CMD=ss.exe get -GL"%vsstrgdir%" -GF- -I-Y -W "$/%var3%" -V"%var4%" VSS_EXTRACT_TARGET=%targ%\%var2%\%fnbksl%(%var3%)\%var4%\%fnfile%(%var1%) BSSRC=\\Critias\BugslayerSource SRCSRVTRG=%VSS_extract_target% SRCSRVCMD=%VSS_extract_cmd% SRCSRV: source files --------------------------------------- c:\code\cppapp\cppapp.cpp*BSSRC*CppApp/CppApp.cpp*First Version c:\code\cppapp\stdafx.h*BSSRC*CppApp/stdafx.h*First Version c:\code\cppapp\stdafx.cpp*BSSRC*CppApp/stdafx.cpp*First Version SRCSRV: end ------------------------------------------------
The ini section specifies the source-indexing version, the version control system used, and the time at which the indexing was performed. The debuggers look for the SRCSRVCMD value in the variables section and treat values surrounded by percent signs as variables to build up. In Figure 4, VSS_EXTRACT_CMD contains the command line that will be built up to call the version control system with the appropriate file and directory to extract to. The source files section shows the values used to fill the variables. Each line contains the data for a single source file, delineated by asterisks. The first value is the path to the source file as it came from the PDB. The second value, in this case BSSRC, indicates the version control database to use. The third value is the version control project, and the last is the file version in version control. SRCSRV.DOC illustrates all this.
At this point, I'm ready to start debugging, but given all the fiddly steps necessary to get source indexing working, I wanted to give the checklist in Figure 5 to follow to achieve Source Server nirvana. I hope that this will save you some time getting started.
Figure 5 Source Server Indexing Checklist
|Is your source tree in a directory below root?|
|Have you built both debug and release builds?|
|Have you built all binaries and PDBs to a logical directory structure?|
|Did you add your version control logical name(s) to SRCSRV.INI?|
|Is SRCSRV.INI in the tools directory, the SRCSRV_INI environment variable, the current directory, or specified with the /Ini switch?|
|Is your version control system command-line tool in the path?|
|For Visual SourceSafe, have you set the SSDIR environment variable?|
|For Visual SourceSafe, is the working directory set for each project?|
|For Visual SourceSafe, have you applied a label to the project?|
|Is Perl 5.0 or greater installed and in the path?|
|Are the Source Server tools and scripts in the path?|
Debugging with WinDBG
Now the moment you've been waiting for! Let's look at how you can use the source server in the debuggers. Turning on source server support in WinDBG, NTSD.EXE, or CDB.EXE is similar to turning on symbol server support. To turn it on globally for all WinDBG sessions, start WinDBG with no debuggee open and then select Source File Path from the File menu to get to the Source Search Path dialog. Alternatively, you can set the _NT_SOURCE_PATH environment variable. In both cases, the value should be SRV* Cache Dir;.
SRV tells the debugger that you're expecting to use a source server and the bit after the asterisk specifies the directory where you want the local cache for all the files. I strongly recommend setting a download cache. If you don't, the sources will be pulled to a Src directory in the Debugging Tools for Windows installation directory. And since the default directory is in Program Files, the pull will abort if you're correctly running in a Least User Access environment. In addition to setting source paths globally, you can always set the path during a debugging session with the .srcpath command.
Once you start debugging in WinDBG and do any operation where you need source code, the dialog shown in Figure 6 will pop up. If you select "Perform this action each time from now on" that will write the decision to the Default value in HKCU\Software\Microsoft\Source Server\Warning so you never see the security dialog again.
Figure 6** Turning Off the Security Alert **
You'll probably want to turn off the security warning and have WinDBG jump directly to executing the version control command to get the source. By using the source server, that's the first place the debugger will look for source, no matter what. If you have a fast version control system, that won't be a problem, but not everyone has a fast system.
An alternative way of disabling the security dialog is to put a copy of SRCSRV.INI in the Debugging Tools for Windows program directory. That's where SRCSRV.DLL resides, and it knows to look for the file there. If SRCSRV.INI has the version control system in the [trusted commands] section, it will skip the security dialog. For each trusted command, you specify the base name of your version control system command-line program on the left. On the right, you specify the full path to the version control system command-line program. In my scenario, the [trusted commands] section looks like this:
[trusted commands] ss.exe=C:\VSNET8\Microsoft Visual SourceSafe\ss.exe
I used a source path of SRV*C:\Code\Source. In Figure 6, you can see the entire path to which version control will extract the file: C:\Code\Source\BSSRC\CppApp\CppApp.cpp\First Version. The C:\Code\Source\ portion is obvious, but the rest of the string identifies the particular file. The BSSRC directory matches the tag value I specified in the SRCSRV.INI file I used to index the PDB files. The CppApp directory identifies the version control project and CppApp.cpp identifies the source file. In this example, I indexed the source by specifying the /Label= "First Version" command-line option. For version control systems other than Visual SourceSafe, if you don't specify the label, you would see the raw file version number. Like a symbol server, Source Server uses the file system as a database in order to uniquely identify the version of the file I need. If the source server code in SRCSRV.DLL sees that the file already exists in the extract directory, it will load the source file from there without querying version control.
I like to set the Source Server cache directory to a directory under my symbol server cache directory. If my symbol server directory is C:\SYMBOLS, my Source Server cache directory is C:\SYMBOLS\SOURCE. In this fashion, all my caches are in the same place, and to reclaim disk space I can simply wipe out everything and start over.
Once I click the Yes button in the Microsoft Source Server Security Alert dialog, provided everything is OK, WinDBG pops up with the source code. If there's a problem, use the .srcnoisy command to have WinDBG report the results of running the version control command-line tools.
Debugging in Visual Studio 2005
Setting up Source Server debugging in Visual Studio 2005 is nearly as simple as it is in WinDBG. Instead of using an environment variable, the magic is in the Options dialog. Once you get to the Options dialog, navigate to Debugging | General (see Figure 7). Check the "Enable source server support" and "Print source server diagnostic messages to the Output window" options. Then click OK and you'll be notified that using Source Server is a potential security problem. Click Yes and you're on your way.
When you start debugging with Visual Studio, a security dialog like the one shown in Figure 8 is displayed. This lets you choose whether to execute the program. Unfortunately, it offers no option to say you trust the symbol server and don't want to see this dialog again, so you'll see it for every source file. There is the option, however, to place a SRCSRV.INI file that defines your [trusted commands] section with your version control system in the Visual Studio installation directory\Common7\IDE directory and you'll never see this security dialog again.
Figure 7** Turning on Source Server in Visual Studio **
Figure 8** Debug Security Check **
You may be wondering where Visual Studio puts the source server download cache. The default location is C:\Documents and Settings\Username\Local Settings\Application Data\SourceServer. This is great because with proper security in place only logged-in users can see that directory. Though my login account is never an admin account, I do want to share the source server cache directory with WinDBG, so I share the source between the debuggers. While I can change the WinDBG source path to SRV* C:\Documents and Settings\Username\Local Settings\Application Data\SourceServer, I'd like to keep the source files under my symbol server directory.
This problem began my quest to figure out where Visual Studio sets the cache directory. Looking at the registry, I eventually found the SourceServerExtractToDirectory under HKCU\Software\Microsoft\VisualStudio\8.0\Debugger—this is where the cache directory setting resides.
Fortunately, the Import and Export Settings Wizard in Visual Studio will properly export and import the settings between machines. Note, if you import the settings to a new machine without the cache directory, the Source Server file pull will silently fail. Always make sure the cache directory exists.
If you are having trouble using Source Server with Visual Studio, make sure to look at the Output window. There you'll see the reports of any problems with Source Server, and it's the first place you should check when you encounter any odd behavior. In addition, you can verify where the source file is located by moving the mouse cursor over the source file tab in Visual Studio; the tool tip will display the full path to the open file. Finally, like WinDBG, Visual Studio will grab the source code through the Source Server before it will search the raw paths in the PDB file.
My last tip about debugging with Visual Studio 2005 is that WinDBG generally has two public releases a year, while Visual Studio has fewer. As I mentioned in the beginning of the article, the 22.214.171.124 version of WinDBG comes with a newer version of SRCSRV.DLL than the one with Visual Studio. I always copy the latest x86 version of WinDBG to the Visual Studio installation directory\Common7\IDE directory so I'm always using the latest version. And you'll need the 32-bit version even if you're using 64-bit Windows since Visual Studio 2005 is a 32-bit x86 program.
Better and Easier Indexing
I've covered all the work you'll need to do to use Source Server. But if you're like me, you quickly realized that while you include a CMD file in your build, you've got better things to do than look through the output of source indexing. Once you hit about, say, five binaries in your project, your eyes will glaze over and you'll miss important stuff. It would be great if there was an automated way to search for possible errors. Not to mention, the source indexing portion should be part of your build, so that's screaming to be yet another MSBUILD task. In my most recent column (msdn.microsoft.com/msdnmag/issues/06/03/bugslayer), I presented some MSBUILD tasks for automating unit testing and code coverage. I want to build on that code with tasks to handle source indexing.
My requirements are simple. If any problem is encountered when indexing source files, the process should generate an error so MSBUILD stops. These problems can manifest in a few ways: any kind of [ERROR] output from SSINDEX.CMD, any PDB file indexing that reports zero source files found, and if there are no source files indexed at all (an insidious problem, for sure).
My plan was to build a usable base task, SourceIndexTask, from which all version control product-specific tasks could inherit. I wanted to do the VssSourceIndexTask too. If you do write a task for a Source-Server-supported version control system, please send it to me and I'll include it in Bugslayer.Build.Tasks.DLL and post the code for all MSDN®Magazine readers.
To keep things simple, I'll assume you have the Source Indexing tools in the path. The base SourceIndexTask will find the tools in the path as MSBUILD expects the full path in order to run. My second assumption is that your directory setup matches your version control project names. While the product-specific indexing scripts let you set project and client parameters, it's a better idea to have the directory and project names align so you can run the indexing script at the top directory. If there are mismatches, you'll have to run the source indexing separately on each directory.
SourceIndexTask has three properties that you must set in your PROJ files: symbols, sources, and VersionControlSystem. Symbols defines the directory where your PDB files are located (this can be a relative path from your PROJ file). I made sources a required property because the indexing commands work better if the task switch to the sources directory is known before execution. The VersionControlSystem parameter is not marked with RequiredAttribute because I didn't want derived classes, such as VssSourceIndexTask, to have to specify the version control system redundantly. However, if VersionControlSystem is not defined, the task will error out as though it were required.
The first of the optional properties for SourceIndexTask is IniFile. This lets you explicitly specify the SRCSRV.INI file. The AdditionalCommandLineOptions property lets you add any commands not covered by derived types. This is also useful if you're using a version control system that does not have its own task class and you want to start indexing immediately. You can add the version control-specific files to the AdditionalCommandLineOptions and be up and running in no time. Obviously, there's no error checking on the values inside AdditionalCommandLineOptions. The last of the optional properties is IgnorePdbFiles. This ITaskItem array is where you can put PDB files when you want to have the tasks ignore the dreaded "zero source files found" error message. This can happen on autogenerated binaries and items like the VC?0.PDB type files I described earlier. The SourceIndexTask already knows to ignore VC?0.PDB, but this property gives you the place to add any additional PDBs you don't want error checking performed on.
The VssSourceIndexTask, which is the task that handles Visual SourceSafe, defines two required parameters, Server and Label. Server holds the version control system. The task sets the SSDIR environment variable before executing the indexing commands to work around the bug in the Visual SourceSafe processing.
The Label property sets the indexing commands to use the specific Visual SourceSafe label. If you are indexing source code from a project that's below the top level in Visual SourceSafe, the indexing will not work. If you do not set the Project property, the VssSourceIndexTask will take the Source property, expand it to the full path, and convert that path to the Visual SourceSafe project. If your Visual SourceSafe projects map to your file directories, which you should always do anyway, this will automatically get the /Project option set with no effort on your part. If you do set the Project property, that value is sacred and not changed by the task, so VssSourceIndexTask passes it directly to the source indexing tools.
One issue with the Project property is if you are working with projects directly off $/ (the Visual SourceSafe root) you will need to manually set the Project property to / in your MSBUILD project. I've racked my brain on how best to handle subproject indexing and this is the best I've come up with. If you have a better idea, please feel free to contact me!
As I mentioned earlier, the source indexing tools for Visual SourceSafe default to the current project, which will cause source indexing to fail when you run on a different project. To work around this problem, the VssSourceIndexTask calls SS.EXE with the cp command line to set the current project to the Project value. Now you don't even have to think about the current project, and you'll have much better luck indexing source code!
To see all the magic in action, look at the MSBUILD project, Index.Proj, in the root directory of the source code. This project indexes the source code from this article.
From an implementation standpoint, there's not much excitement going on. The most interesting challenge was figuring out how I could look for errors in the source indexing output. I originally thought I'd redirect the output to a file and use my ninja regular expression skills to parse up all the problems. However, as I was deriving the SourceIndexTask from Microsoft.Build.Utilities.ToolTask, which is the killer class for command-line tools, I observed that the class sets up a standard I/O reader. Whenever a command-line tool writes a line of output, the ToolTask calls the overridable LogEventsFromTextOutput method. This meant that all I needed to do was set up a tiny state machine to look for errors, warnings, the PDB file being processed, and the processing result. The big work was going on to find all the issues with the source indexing tools so I could account for them.
I'm sure that, after having read this, you understand the importance of Source Server. Along with the symbol server, you're armed to tackle any debugging challenge with the best information possible. There are a few rough edges to work around, but you'll notice improved productivity immediately. It won't take long before you wonder how you ever debugged without it!
John Robbins is a cofounder of Wintellect, a software consulting, education, and development firm that specializes in the .NET and Windows platforms. His latest book is Debugging Applications for Microsoft .NET and Microsoft Windows (Microsoft Press, 2003). You can contact John at www.wintellect.com.