Wintellect  

The estimable Raymond Chen posted a funny story today about an experience he had in Sweden. (Make sure you read Raymond's post so you can understand one of the greatest blog comments ever!) Anyway, I had to laugh out loud because I had something similar happen to me in Sweden as well. A number of years ago, I had some work in Stockholm Sweden, but as I had never been, I arrived a few days early so I could do a little sightseeing and enjoy the culture.

Having read up on Stockholm before arriving, the first place I wanted to see was Gamla stan, the old town. Right after throwing my bags into my hotel room, I made a beeline down there on the amazing Swedish public transportation. There was only one small problem, I didn't get to pick the time for my business trip. It was December. It was also, as the hotel clerk informed me, one of the coldest days Stockholm had seen in 25 years.

No big deal. I grew up in Alaska so knew what cold was. I was also living in New England at the time so I made sure to pack nice warm clothes and was prepared. When I started walking around, I realized that it was not just cold it was seriously cold. Something like -15 F with a nice stiff wind blowing. I was doing OK, but my camera's batteries were dying after one or two pictures so I had to keep the camera in my coat. The consolation was that there were no other people at all out so all my building pictures were unobstructed.

I'd been poking around for an hour or two and was standing in a decent size courtyard that was about the size of a football pitch (a soccer field to my American readers) and the light starting to get nice for pictures. As I was waiting for a little better light, I was looking at my map. Out of the corner of my eye, I noticed a bundled up woman walking vigorously in my direction.

The woman walked right up to me and said in English "Are you lost? Can I help you? Are you OK?" all in a big rush. I told her that I was fine but waiting for the sun to go down a little more so I could get a better picture of one of the buildings. She looked at me a little strange.

I then asked her "How do you know to speak English to me?"

Her response: "Only an American would be crazy enough to be out taking pictures on a day so cold even us Swedes are smart enough to stay inside!"

With that she turned on her heal and marched quickly away with my uncontrollable laughter in the background.

Aren't "New and Improved" the most over used, and contradictory, terms in the marketer's dictionary? How can something and be new, but yet improved because if it is improved that cancels out the new. My point is that I want to tout an updated release of Paraffin, but I guess I'm being too much of an engineer instead of a marketer which is not a good idea when you something new and improved. Argh! I don't think I'm helping myself here much. How about I express it this way; Paraffin has some improved things and some new things both of which I think users will greatly appreciate.

For those of you wondering what Paraffin does it's a tool I wrote to help produce Windows Installer XML (WiX) fragments of files in your installations. The big feature of Paraffin is that is automates the management of your files because it keeps consistent the component ids and GUIDs as well as file and directory ids as you add files to your installer. Component GUID management is one of the biggest pain points on any WiX projects, especially those that have many files to install.

For background, I'll refer you to the WiX 3.0 documentation, as well as the articles I wrote on the motivation behind Paraffin, Part 1, Part 2, and Part 3. Those articles were written for WiX 2.0, but I updated Paraffin with full to WiX 3.0 support back in December 2008 when WiX 3.0 went to Beta. Paraffin started out as just something that scratched my itch and gave me a chance to play with the brand new LINQ to XML stuff in Visual Studio 2008 Beta 2. It's been gratifying how many people have told me they have used Paraffin and, even better, asked for feature enhancements. Keep those complaints and requests coming!

Many people have asked about the status of Paraffin now that WiX 3.0 has a much-improved Heat program that handles updating fragments. First off, I am thrilled that Brian Rogers is making Heat much more capable and useful. The WiX team as a whole has done an amazing job of making installers easier to write for mere mortals. Anything that can wrap the interesting API known as Windows Installer is always a good thing. With Heat now doing many of the things Paraffin does, and way more, if you're in the planning stages of your install you need to seriously look at the whole WiX approach to auto generating fragments. The WiX developers are much smarter than I am and are thinking hard about how to solve these difficult problems. Paraffin meets my needs, and may meet yours, but definitely look at the Heat approach.

So let's get on with the new and improved. In talking with several people that use Paraffin for their installs, I found that everyone wanted the -guids option, which told Paraffin to put in the GUIDs for components, to be on by default. That was a simple enough change, but if you are updating a file created with 3.0 and you didn't want automatic GUIDs for components, I still respect that setting so you can add your GUIDs manually. I fixed a few other smaller things. The first was that I now do not include hidden files in the output. Also, I made all file and directory comparisons case insensitive to avoid Paraffin seeing the directory as different than what was previously processed on an update.

As I was working on the code, I decided to remove the option to upgrade your WiX 2.0 Paraffin produced files to WiX 3.0. As nearly everyone is now on WiX 3.0 and the conversion is a onetime operation, I probably am not upsetting too many people. Additionally, the conversion code was making development more complicated. If you're still working with WiX 2.0, download Paraffin 1.04. If you need the conversion of WiX 2.0 Paraffin produced files to WiX 3.0, download Paraffin 3.0.

My original intent with Paraffin was a tool you would use to take directories of files you want to install and make that easy to maintain. My thinking was that you would do all your main INSTALLDIR portions manually. However, it turns out many, many, many people want to point Paraffin at all their files and essentially generate the fragment for everything under INSTALLDIR. The problem was that Paraffin 3.0 and prior would always insert the Directory element specified on the command line. Well, no more! <grin>. If you run the initial Paraffin run with the new -norootdirectory switch, Paraffin will start everything under the DirectoryRef element. That new and improved feature should make lots of people happy!

Looking at how Heat tackled the Component and File Id attributes problem was enlightening. Paraffin had originally used what I called a custom value, along with an incrementing counter to generate values for the Id attribute. That worked fine, but several people had reported that on very large projects, you could end up with Id collisions, which is not happiness in Windows Installer land. Heat is now using GUIDs for Id attributes, through a cool custom generation scheme. I decided to go a similar route, but since I did my updating based on filenames instead of directories, I felt it was safe enough to use normal GUIDs.

The downside is that you lose readability on the Id attributes when looking at Paraffin generated .WXS files. This will also affect WiX error message readability as well. However, I felt the tradeoff was worth it because you’ll never have to worry about Id attribute conflicts again and it allows me to introduce a new feature, which I’ll discuss in a second. I dropped the previous -custom switch in favor of -groupname to specify the Id attribute of the ComponentGroup. If you have scripts that call Paraffin, I redirect the -custom switch to -groupname. I’ve tried to keep backwards compatibility in mind.

For those of you like me who have a bunch of Paraffin produced files using Paraffin 3.0, what does this new Id attribute naming scheme mean when you update a file? Fortunately, nothing. I’ve introduced the concept of file versions in the comment where I store the file creation options. If the file is a Paraffin 3.0 file, it has no version so I use the old Id attribute-naming scheme for consistency. It wasn’t that hard to code and it keeps backwards compatibility. For all newly created Paraffin .WXS files, you obviously get file version 2 so I use the new GUID based scheme.

Andriy Volkov had a very interesting idea for a feature where Paraffin would look for specific files as it was recursing directories, and insert their contents into the current Directory element. That sounded like an excellent idea to me. This feature is what dictated changing the Id attribute scheme, as I wanted to avoid conflicts. With the file insertion capabilities now, you can put things like COM and service registration next the files themselves to make your installs more modular.

Paraffin looks for .ParaffinMold files, which are nothing more than WiX fragments. That way you have the full .XSD file magic when creating the files to avoid errors. What Paraffin looks for the DirectoryRef element in the .ParaffinMold file and inserts all child elements directly into the output file. Paraffin does not use the DirectoryRef Id attribute nor is there any validity checking. Below is an example of a .ParaffinMold file that will insert a Component element that creates a registry key.

<?xml version="1.0" encoding="utf-8"?>
<
Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
    <
Fragment>
        <
DirectoryRef Id="INSTALLDIR">
            <
Component Id="HappyID"
                       Guid="PUT-GUID_HERE">
                <
RegistryKey Root="HKLM"
                       Key="SOFTWARE\My Company\My Product">
                    <
RegistryValue Name="InstallRoot" Value="[INSTALDIR]"
                Type="string" KeyPath="yes"/>
                </
RegistryKey>
            </
Component>
        </
DirectoryRef>
    </
Fragment>
</
Wix>

So there’s the new and improved Paraffin! Please let me know how it works for you. As always, please let me know if you have more feature requests for Paraffin. I’d also love to know what you think of Paraffin. Thanks for using it! Grab version 3.1 from this link.

[Edit 6/29: Per Rob Mensching's comment below, fixed the RegistryKey example.]

After a detour into Historical Debugging, it’s time to come back to return to answering questions about PDB files. Here’s a question from Justin:

Thanks for the great post once again. I was looking forward to your debugging virtual training, but unfortunately it was cancelled.

The company I work for is pushing pack against building release mode binaries with debug information generated, one of the reasons I signed up for the class :). They are afraid performance will be affected.

My question is what are the best command line arguments for generating symbols in release mode? Also is there somewhere I can reference to show that there should be no performance hits.

I’m sorry about the canceled class, but the good news is that Mastering .NET Debugging was rescheduled to July 14-15.

The executive summary answer: no, generating PDB files will have no impact on performance whatsoever. As for references that I can point you too, I haven't found any on the web that answer the exact question so let me take both .NET and native development in turn.

Recently, the always-readable Eric Lippert wrote a great post What Does the Optimize Switch Do? where he discusses the optimizations done by the compiler and Just in Time (JIT) compiler. (Basically, you can sum it up as the JITter does all the real optimization work.) There's a bit of confusion on the C# and VB.NET compiler switches around as there are four different /debug switches, /debug, /debug+, /debug:full, and /debug:pdb-only. I contributed to that confusion because I thought /debug:pdb-only did something different that was better for release builds than the other three /debug switches.

All four switches all do the same thing in that they cause a PDB file to be generated but why are there four switches to do the same thing? Do Microsoft developers really love parsing slightly different command line options? The real reason: history. Back in .NET 1.0 there were differences, but in .NET 2.0 there isn't. It looks like .NET 4.0 will follow the same pattern. After double-checking with the CLR Debugging Team, there is no difference at all.

What controls whether the JITter does a debug build is the /optimize switch. Building with /optimize- will add an attribute, DebuggableAttribute, in the assembly and setting the DebuggingMode parameter to DisableOptimizations. It doesn't take a Rhodes Scholar to figure out that DisableOptimizations does exactly what it says.

The bottom line is that you want to build your release builds with /optimize+ and any of the /debug switches so you can debug with source code. Read the Visual Studio documentation to see how where to set those switches in the different types of projects.

It's easy to prove these are the optimal switches. Taking my Paraffin program, I compiled one build with /optimize+ and /debug, and another with just /optimize+.

which is the same as /debug+ and /debug, and the other with /optimize+ /debug:pdbonly to show the differences, which is the root of how we got it wrong. After compiling, I used ILDASM with the following command line to get the raw information from the binaries

ILDASM /out=Paraffin.IL Paraffin.exe

Using a diff tool you'll see that the IL itself is identical between both builds. The main difference will be in the DebuggableAttribute declaration for the assembly. When built /optimize+ and a /debug switch, a DebuggingMode.IgnoreSequencePoints is passed to the DebuggableAttribute to tell the JIT compiler that it doesn't need to load the PDB file in order to correctly JIT the IL. A value of DebuggingMode.Default is also OR'd in, but that value is ignored.

Like .NET, building PDB files has nothing to do with optimizations so have zero impact on the performance of an application. If you have a manager who in Justin's words is "afraid performance will be affected" here's what I tell them.  (Sadly, I've run into a few more managers who say that than I care to count).

That might be true on other operating systems, but not Windows. If you think they do, then why does Microsoft build every single product they ship with PDB files turned on for both debug and release builds? They wrote the compiler, they wrote the linker, and they wrote the operating system so they know exactly what the effects are. Microsoft has more people focused on performance than any other software company in the world. If there were any performance impact at all, they wouldn't do it. Period. Performance isn't the only thing at Microsoft, it's everything.

Where .NET is pretty simple as there's really only two switches, the appropriate optimization switches are dependent on many individual application factors. What I can tell you is what the switches you need to set to generate PDB files correctly in release builds.

For CL.EXE, the compiler, you need to add /Zi to have it put debugging symbols into the .OBJ file. For LINK.EXE, the linker, you need to specify three options. The first is /DEBUG, that tells the linker to generate a PDB file. However, that switch also tells the linker that this is a debug build. That's not so good because that will affect the performance of your binary. Basically what happens when you use /DEBUG is the linker links faster because it no longer looks for individual references. If you use one function from a OBJ the linker throws the whole OBJ into the output binary so you now have a bunch of dead functions.

To tell the linker you want only the referenced functions, you need to add /OPT:REF as the second switch. The third switch is /OPT:ICF, which enabled COMDAT folding. There's a term you don't hear every day. Basically what this means is that when generating the binary, the linker will look for functions that have identical code and only generate one function but make multiple symbols point to the one function.

If you want to test the difference yourself on a native binary to see what affects generating PDB files will have, it's nearly as easy as a .NET binary. Visual Studio comes with a nice little program, DUMPBIN, which can tell you more than you ever wanted to know about a Portable Executable file. Run it with /DISASM switch to get the disassembly of a binary.

Please keep those PDB related questions coming. Of course, if you have any other questions, I'll be happy to take a crack at those also. Gee, I better draw the line: no investment or relationship questions. <grin>

Visual Studio 2010's Historical Debugging feature fascinates me. As you sit in the debugger, being able to move backwards while in the debugger (or attach the execution log to a bug) has to be the killer feature coming up in the entire Visual Studio 2010 release. Sure, there are some nice things with Silverlight, TFS, and WPF, but they are all secondary in my opinion. Of course, I'm extremely biased as I love all things diagnostics, but you do have to agree anything that lets you fix bugs easier is always the feature you really need at 3AM.

Using Visual Studio 2010 Beta 1, I wanted to describe how Historical Debugging worked because I feel Historical Debugging will change the way you debug so it's worth starting to look at it now. Also, well, it's just very cool to figure out how something works. I'm not going to cover much about how you use Historical debugging because it's mostly obvious how it works and everyone has access to the Beta 1 bits. If you haven't had a chance to install and use Historical Debugging, Habib Heydarian, PM of all things Diagnostics, has a great overview on his blog.

What I'm going to discuss here is the two sides to Historical Debugging. The first part is how Historical Debugging gets its hooks into your code so it can record what's going on. The second part is how Historical Debugging decided what events to report and shows how you can extend the reporting model.

As you can imagine, there are all sorts of huge disclaimers on what I'm going to talk about here. This article is based on Beta 1 and is all subject to change so nothing is guaranteed to ever work this way again. Additionally, if you rely on anything in the article it could cause your dog to run off, your plants to die, and you to develop fege bosta so bad nothing will help

Before we dive in, I do have to bring up one disappointing facet to Historical Debugging; it's 32-bit only. I've been in this business long enough to know software development is all about tradeoffs and "Shipping is a Feature" so something had to give during scheduling. However, my personal opinion is that I would have gladly traded the WPF UI for full x64 support across the all tools in Visual Studio, more diagnostic features, project file compatibility between VS 2008 and VS 2010, more TFS features, and speed, speed, and more speed. Don't get me wrong, Visual Studio 2010 will be a huge landmark release, but I think a wholesale UI change could have waited for another release or two. (Again, I definitely don't speak for Microsoft, and I don't speak for anyone else at Wintellect.)

The place to start with Historical Debugging is the Options dialog as that's where everything is controlled. The first decision is whether you want to record events only or Events, Methods and Parameters.

Obviously, selecting Events, Methods, and Parameters will record much more information. Some rough calculations on the data file sizes show that Event only files are about 20% the size of full information files. As this is Beta 1, I didn't bother doing any performance tests for the different collection types.

The tantalizing portion of the above dialog are the three grayed out buttons because they allude to the fact that while not supported in Beta 1, future Betas and releases will allow us to control exactly what information we want collected. I can't wait to see what future releases will allow us to control. Can you imagine how cool it will be if have a hypothesis about a production-only problem and you can develop a collection plan that looks for just that information on the production server without killing performance? That will really make some of those debugging challenges we all face much easier. Now you see why I'm so interested in Historical Debugging!

For Beta 1, the two interesting Options pages are Diagnostic Events and Modules. The Modules is easiest to understand because that's where you tell Historical Debugging which modules you want to collect data from or exclude from data collection. If you add modules to the Modules list the values you add are stored in the solution's .SUO file for Beta 1 so if you delete the .SUO, you lose your settings.

The Diagnostic Events page is the more exciting set of options because it shows you what Beta 1 supports out of the box for the different events recorded by Historical Debugging. While you can enable and disable individual categories and items, Beta 1 doesn't offer a way to add or remove events through the UI. What lead me on my journey digging into how Historical Debugging works is I wanted to find if I could add my own items to the Diagnostic Events page.

When you are debugging with Historical Debugging turned on, you'll see two additional DLLs in your address space, which you can see with Process Explorer. The first is a .NET assembly from the GAC, Microsoft.VisualStudio.TraceLog.Runtime.DLL, which I'll talk more about in a minute. The second is a native DLL, TraceLogProfiler.DLL, which version information description is "VS Logger CLR Profiler." The description gives away that Historical Debugging is done through a CLR profiler, but what is that profiler doing? As CLR 4.0 profilers can still use the COR_PROFILER environment variable, I saw the value 301EC75B-AD5A-459C-A4C4-911C878FA196, which looking in the registry got me to its COM registration, where the default value gives yet another hint: "Visual Studio Trace Debugger Instrumentation."

It's probably not too big a surprise that Historical Debugging is using IL rewriting to inject its hooks into the application as about the only other way would be to require you instrument the IL on disk before you run the binary. Peeking at what the instrumentation looks like is relatively easy with WinDBG and a little SOS magic. For those of you that haven't done any SOS work with WinDBG and CLR 4.0, the basic idea is still the same. The only difference is that instead of MSCORWKS.DLL being the key DLL, it's now CLR.DLL. Thus, to load the correct SOS extension, use .loadby sos clr and you'll be on your way.

The snippet of disassembled code below shows examples of the instrumented code for a Windows Forms Main method where I've turned on collection for events, methods, and parameters. I highlighted in red the code injected by Historical Debugging.

0:013> !u 14c8720c
Normal JIT generated code
AnimatedAlgorithm.AnimatedAlgorithmForm.Main()
Begin 14d4bb90, size 70
14d4bb90 push ebp
14d4bb91 mov ebp,esp
14d4bb93 push eax
14d4bb94 cmp dword ptr ds:[14C79D34h],0
14d4bb9b je 14d4bba2
14d4bb9d call clr!JIT_DbgIsJustMyCode (71773bfb)
14d4bba2 mov ecx,2
14d4bba7 call 1162ea70 (Microsoft.VisualStudio.TraceLog.TraceRuntime. 
                        AddEnterNoParametersMethod(Int32), mdToken: 06000172)
14d4bbac nop
14d4bbad mov edx,1
14d4bbb2 mov ecx,2
14d4bbb7 call 1483a760 (Microsoft.VisualStudio.TraceLog.TraceRuntime.
                        AddCallSite(Int32, Int32), mdToken: 06000175)
14d4bbbc mov ecx,14C872CCh (MT: AnimatedAlgorithm.AnimatedAlgorithmForm)
14d4bbc1 call clr!JIT_NewCrossContext (7151890e)
14d4bbc6 mov dword ptr [ebp-4],eax
14d4bbc9 mov ecx,dword ptr [ebp-4]
14d4bbcc call 1269f214 (AnimatedAlgorithm.AnimatedAlgorithmForm..ctor()
14d4bbd1 mov edx,6
14d4bbd6 mov ecx,2
14d4bbdb call 1483a760 (Microsoft.VisualStudio.TraceLog.TraceRuntime.
                        AddCallSite(Int32, Int32), mdToken: 06000175)
14d4bbe0 mov ecx,dword ptr [ebp-4]
14d4bbe3 call dword ptr ds:[2606608h] (System.Windows.Forms.Application.
                            Run(System.Windows.Forms.Form), mdToken: 060012e4)
14d4bbe9 nop
14d4bbea int 3
14d4bbeb nop
14d4bbec mov ecx,2
14d4bbf1 call 14d4b750 (Microsoft.VisualStudio.TraceLog.TraceRuntime.
                       AddLeaveNoParametersMethod(Int32), mdToken: 06000174)
14d4bbf6 call 14d4b9f0 (Microsoft.VisualStudio.TraceLog.TraceRuntime.
                        DumpResult(), mdToken: 0600017d)
14d4bbfb nop
14d4bbfc mov esp,ebp
14d4bbfe pop ebp
14d4bbff ret

For those of you paying close attention to the disassembly, the CLR JIT compiler defaults to a fastcall calling convention so ECX is the first parameter and EDX is the second parameter. Looking with Reflector at Microsoft.VisualStudio.TraceLog.Runtime.DLL makes it easy to see what the key TraceLog class expects as parameters. For example, the AddCallSite method, takes a metadata token and an offset as its two parameters. The job of these injected method calls are to collect the minimal data and get it to the logger.

By the way, Reflector works with CLR 4.0, but does not correctly find assemblies in the GAC as the internal GAC directory structure has changed. To load an assembly from the CLR 4.0 GAC, you'll have to find the assembly in a PowerShell window manually to specify the full path in the Open dialog. I'm sure Red Gate will have an update to Reflector around the Beta 2 timeframe.

Winding through a method like TraceLog.AddCallSite shows that the information is packed up and put on a named pipe. I won't bother grinding through how that works because there's nothing too exciting about it. The interesting thing is that if you look with Process Explorer at a Windows Forms application, you'll see the process start hierarchy is DEVENV.EXE, the debuggee process, and VSTRACELOG.EXE. From the name, you can guess that is where the actual logging occurs. Peaking at the handles for the debuggee and VSTRACELOG.EXE shows named events and sections (memory mapped files) shared between the two processes.

As the debuggee starts VSTRACELOG.EXE, the key coordination information has to be passed through either environment variables or command line parameters. Process Explorer makes it easy to see the command line parameters (which I've put each on their own line)):

"C:\VSNET10\Team Tools\TraceDebugger Tools\\VSTRACELOG.EXE"
run
/n:"animatedalgorithm.exe_0000058c_01c9edf3f5c6b945"
/cp:"C:\ProgramData\Microsoft Visual Studio\10.0\
     TraceDebugging\Settings\gnlg3dip.3pv"
/f:"AnimatedAlgorithm_090615_130041.tdlog"

To see what those command line values mean, I figured it couldn't hurt to try VSTRACELOG.EXE run /? and it turns out to work! Sometimes reverse engineering is mastering the obvious. Here's what each value means:

  • run: Runs a logger.
  • /n: The logger name.
  • /cp: The collection plan to use
  • /f: The name of the log file.

The paths specified in the above command line looked to be the same value specified in the Options dialog, Historical Debugging node Advanced page, Location of Historical Recordings edit control. Of most interest to me was /cp because that's when I looked at GNLG3DIP.3PV, it is actually an XML file that contains some runtime setting information. Mostly the file has a bunch of elements that look to be the events recorded by Historical Debugging because the values line up with those displayed in the Diagnostic Events in the Options Dialog I showed earlier. As the filename is obviously a temporary filename, I went on a little quest to see what generated the collection plan file for the run.

Running Process Monitor as I started a new debugging session it was easy to figure out what was going on. DEVENV.EXE was reading from the HKCU\Software\Microsoft\VisualStudio\10.0\DialogPage\Microsoft.VisualStudio.TraceLogPackage.ToolsOptionDiagnosticEvents\NotifyPoints registry key right before is created the collection plan file. Looking at that key was a little confusing because it's a bunch of REG_SZ values with numbers as the name and True or False as the data.

A more careful look at the Process Monitor log showed DEVENV.EXE reading C:\VSNET10\Team Tools\TraceDebugger Tools\en\CollectionPlan.xml. Aha! That's where the base collection plan is stored for Beta 1. (Note I installed VS 2010 into C:\VSNET10 instead of the default directory.)

Looking at CollectionPlan.XML it isn't too hard to figure out what's going on. At the top of the file is the ModuleList element, which is the list of assemblies shown in the Options dialog, Historical Debugging node, Modules property page. Today, this list automatically has wildcard characters at the beginning and end of the values so it's a big hammer. I suspect we'll see much better control over modules in future beta releases.

The most interesting part of the file starts with the TracePointProvider element. What I'll do to show how a collection plan works is show the pieces for the Console.WriteLine events. To define a new collection category, you need, well, the Categories and sub Category elements.

<Categories>
   <Category ID="console" _locID="category.SystemConsole">Console</Category>
</Categories>

The value of the Category is what will appear in the Diagnostic Events property page.

After the Categories, you need to define a mapping between the assembly name and a module ID in the ModuleSpecification element.

<ModuleSpecifications>
   <ModuleSpecification ID="mscorlib">mscorlib.dll</ModuleSpecification>
</ModuleSpecifications>

The super interesting part of the collection plan starts under the NotifyPointSpecifications element as that's where you define the events themselves.

<NotifyPointSpecifications>

For each event recorded by historical debugging, there is a NotifyPointSpecification. The enabled attribute is true if reporting of this notify point is enabled, false means don't report it.

   <NotifyPointSpecification enabled="false">

The Binding element is obviously how Historical Debugging knows how to hook a particular method. In Beta 1 all of the defined Binding elements have their explicit attributes set to false, which I suspect means to look up the MethodId element values through metadata instead of by token or address. Several of the notification points in the Beta 1 CollectionPlan.XML file show another attribute on the Binding element, onReturn="true", which will show the return value for the event.

      <Bindings>
         <Binding explicit="false">

Next comes the mapping to the ModuleSpecification element described earlier.

         <ModuleSpecification>mscorlib</ModuleSpecification>

Here's the method you want to report the event on by type, method, and complete method id.

         <TypeName>System.Console</TypeName> 
         <MethodName>WriteLine</MethodName> 
         <MethodId>System.Console.WriteLine(System.String):System.Void</MethodId>

The SettingsName element says (0 args) but that is wrong. (It is Beta 1 after all!). The SettingsName value is what Options Dialog, Historical Debugging node, Diagnostic Events property page shows.

         <SettingsName _locID="settingsName.WriteLine">WriteLine (0 args)</SettingsName>

The Name element is what the Debug History window shows when the event is the single line mode and the Description element shows when you click on the event.

         <Name _locID="name.WriteLine">Console Output "{0}"</Name> 
         <Description _locID="description.WriteLine">Console Output "{0}"</Description>

To make the above clear, here's a screen shot showing where the two elements are used.

The Category mapping is presented and the other elements are closed off.

         <Category>console</Category> 
      </Binding>
   </Bindings>

The DataQuery elements are where the real action occurs because that's where parameters and return values are processed so they can be used in the format strings in the Name and Description elements. It's not clear what the index attribute is supposed to be. There are values ranging from -1 to 2 in the Beta 1 CollectionPlan.XML file. I originally thought they would correspond to format indexes, but they don't. I did figure out -1 means a return value and only works if you specify onReturn="true" on the Binding element. The type and name attributes are interesting as those are the values displayed in the Autos window when you click Autos link in an individual event.

         <DataQueries
            <DataQuery index="0" maxSize="256" type="String" name="value"></DataQuery>
         </DataQueries>
      </NotifyPointSpecification>
   </NotifyPointSpecifications>

The above DataQuery is straightforward to pick out, but they get much more interesting. The following snippet from the FileStream.Dispose event shows a value for the element, _fileName, which maps to a field in the FileStream class. If a field is not displayed, the name is assumed to be the parameter or field.

<DataQuery index="0" maxSize="256" type="String" name="fileName">_fileName</DataQuery>

Continuing to poke through the Beta 1 CollectionPlan.XML turned up an extremely interesting option for reporting parameters::

<NotifyPointSpecification>
   <Bindings>
      <Binding explicit="false"> 
         <ModuleSpecification>system</ModuleSpecification> 
         <TypeName>System.Diagnostics.TraceListener</TypeName> 
        
<MethodName>TraceEvent</MethodName> 
         <MethodId>System.Diagnostics.TraceListener.TraceEvent( 
                  
System.Diagnostics.TraceEventCache, 
                   System.String, 
                   System.Diagnostics.TraceEventType, 
                   System.Int32, 
                   System.String, 
                   System.Object[]):System.Void</MethodId> 
         <SettingsName _locID="settingsName.TraceEvent">TraceEvent with format 
                                                        arguments</SettingsName> 
         <Category>tracing</Category>
      </Binding>
   </Bindings>
   <DataQueries />
   <ProgrammableDataQuery>
      <ModuleName>Microsoft.VisualStudio.DefaultDataQueries.dll</ModuleName>
      <TypeName>Microsoft.VisualStudio.DataQueries.Tracing.TraceListener. 
               
TraceEventFormat</TypeName> 
   </ProgrammableDataQuery>
</NotifyPointSpecification>

The ProgrammableDataQuery element gives us a glimpse at the extensibility hooks into event recording. Opening up Microsoft.VisualStudio.DefaultDataQueries.DLL shows that if you derive your class from the IProgrammableDataQuery interface, shown below, you'll be handed the parameters and return values so you can do more analysis to report better data for your events.

public interface IProgrammableDataQuery
{
    // Methods 
    object[] EntryQuery(object thisArg, object[] args); 
    object[] ExitQuery(object returnValue); 
    List<KeyValuePair<string, string>> 
                      FormatCollectedValues(object[] results); 
    string FormatLongDescription(object[] results); 
    string FormatShortDescription(object[] results);
}

All of the information I discussed here will probably all change by the time Beta 2 ships, but it's still worth looking at to give you ideas to start thinking how you can about how to extend Historical Debugging for your particular scenarios. My mind is already working overtime on the tools I might be able to develop to help automate building your own collection plans for custom libraries. Granted building FxCop/Code Analysis rules has always been undocumented (and will continue to be in VS 2010), we can hope for full documentation on how to extend Historical Debugging, but don't be surprised if there isn't any. I can't wait to get my hands on Beta 2, as I'm sure it will be super exciting from a Historical Debugging perspective!

While Silverlight is a functional subset of WPF and the gap is ever narrowing, there are many features that WPF supports that Silverlight does not. Silverlight even offers a few features that WPF doesn't. Microsoft asked us to do a whitepaper on the differences. Sergio Loscialo, with help from Steve Porter and Kate Gregory, has produced a 69-page paper that goes into amazing detail comparing and contrasting the differences. With this paper, you can learn how to write code that's portable between Silverlight and WPF. Read Guidance on Differences between WPF and Silverlight on CodePlex.

As I've discussed, PDB files are wondrous bundles of binary joy. However, loading missing PDB files can quickly become an angst-ridden teenager as you wait what seems like forever on a network timeout for missing symbols. This is especially apparent in Tom's question:

For nearly two years I've been searching the Internet for an answer to this question: is it possible to get the DBGHELP.DLL to /not/ load a PDB for a specific DLL? I have applications that have lots of third-party DLLs for which I have no PDB files (read: "drivers for a database company that shall remain nameless") and I'd like to tell the DBGHELP.DLL to avoid attempting to search for those PDBs. In fact, I have been investigating the possibility of making stub PDBs with no information and stuffing them in a private symbol server just to make symbol loading faster.

Any thoughts?

Oh, I have many thoughts, and like Tom, some of them are not wholesome when wrestling with that "database company that shall remain nameless" because they won't release symbols for their native compiled drivers. For those of working on native binaries that third parties plug into, it's trivial with the linker /PDBSTRIPPED switch to generate the same kind of PDB files Microsoft makes available on its public symbol server. You're not giving up any secrets with stripped symbols!

The good news for all of us needed to skip loading a few PDB files is that the debugger folks at Microsoft know your pain and have a decent solution. Unfortunately, most people haven't seen it because the trick is in the SymProxy discussion, which is how you host your own HTTP symbol download. Mentioned in passing is "This feature exists in the client debugger..." There is one small bug is how they tell you to do the exclusion is incorrect, but easily fixable.

The first way to exclude particular PDB files is to create a file called SYMSRV.INI and put the Exclusions section in like the following:

[exclusions]
dbghelp.pdb
symsrv.*
mso*

I love the wildcard support! The documentation doesn't tell you where to place that file, but it needs to be in the same directory as SYMSRV.DLL. That's why I don't care for this approach because that means you need to put this file in every place where you have a debugger as they all use SYMSRV.DLL to do their symbol loading.

A better approach is to put the exclusions in the registry so every debugger automatically picks them up. The documentation says to put them in HKEY_LOCAL_MACHINE, but the real place is HKEY_CURRENT_USER. First, create a key HKEY_CURRENT_USER\Software\Microsoft\Symbol Server\Exclusions. In that key, create REG_SZ values for the PDB files, including wildcards, for the PDB files you want to skip. Here's an example .REG file for skipping NOTEPAD.EXE and for Tom, the wildcard for what I'm guessing he'd like to skip <grin>:

Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Microsoft\Symbol Server\Exclusions]
"notepad.pdb"=""
"ora*.*"=""

In the value field, you can put anything you want, like a comment as to why you need to skip loading these PDB files. Alternatively, you could fill the void with pithy quotes to provide a moment of Zen for anyone reading your registry.

Using the above .REG file, I debugged NOTEPAD.EXE with Visual Studio and here's the proof that no download from the Microsoft symbol server took place.

There are a couple of more questions in my queue, but if you have more, please don't hesitate to ask if there's something you wanted to know about PDB files. Please let me live my dream of educating everyone about PDB files!

While Microsoft really wants everyone to test doing an upgrade install of Windows 7 from Vista, I don't know anyone who has because all my friends are jaded Windows developers. Having installed Win 7 RC from scratch twice on my laptops, I was feeling quite lazy when it came to my desktop. While I've automated nearly all my application installs with PowerShell scripts it's still four-five hours overall. As I hadn't seen anyone else reporting how upgrade installs went, I thought I'd let everyone know that it went well for me. There were a few very small bumps so I thought I'd mention how I worked around them in case others want to try an upgrade install.

As with any upgrade, have a trusted backup before you start, in my case, that meant two, one in Home Server and one a system back up. I was upgrading my wonderful Mac Pro running Vista Ultimate x64 SP1which had the 2.1 version of Apple's Boot Camp Vista x64 drivers installed.

I had to laugh when I first tried to do the upgrade. The last time I did an in place upgrade of an operating system without a fresh install must have been ten years ago so I rebooted the machine to start the install from the Win7 DVD. When I clicked on the upgrade link, it kindly told me that I needed to start the install from Vista in order to upgrade. Once I did what I was supposed to do, the compatibility report said that both SQL 2005 and the Apple Boot Camp drivers had some compatibility problems, but in the cas e of the Boot Camp drivers, I could reinstall them after the upgrade was complete. As I didn't need SQL 2005 anymore, I uninstalled it and restarted the upgrade install.

The upgrade cranked away and after a couple of reboots, it looked like it as done. I wasn't paying attention to the time at all so I think it was less than two hours. After logging in, it looked like everything was running pretty well.

As reported, the upgrade disabled some of the Apple Boot Camp drivers so the first thing I did was do a repair install, which stopped saying a there was a pending reboot and the install could not continue. I rebooted and still got the same error. It turned out that the HP printer I'm using doesn't have Win7 drivers so something in the system was trying to delete one of the printer driver files every time I rebooted the machine. I deleted the printer, but still kept seeing the HKLM\System\ControlSet\Control\Session Manager\PendingFileRenameOperations value getting reset to delete the bad printer DLL. Once I deleted the values in the REG_MULTI_SZ dialog, the Apple Boot Camp driver install went perfectly so my ultra thin Apple wired keyboard works great.

One odd problem I was having was that it seemed my Bluetooth mouse wasn't responding very well. The mouse would move and click fine, but would stop and I'd have to shake the mouse to get it to respond. If I continually moved the mouse, everything was fine, but if I stopped using it for 5-20 seconds, it felt like the Bluetooth connection was lost. As the exact same mouse was working fine in Vista two hours before I was looking at a battery problem or something had changed with Win7.

A quick look in the Event Log, the go-to place for starting all Windows trouble shooting, showed a bunch of HidBth warnings saying "Bluetooth HID device (xx:xx:xx:xx:xx:xx) either went out of range or became unresponsive." It sure felt like the Bluetooth device was going to sleep. Popping open Device Manager, I looked at the Apple Built-in Bluetooth Properties page, Power Management tab. The "Allow the computer to turn off this device to save power" check box was checked. I unchecked it, clicked OK, and haven't had a Bluetooth mouse problem since.

As I opened the Event Log, I got an error in the default Administrative Events filter. On Vista there must be some additional event logs because using that filter pops up a "Query Error" saying it can't find the following event logs:

  • Microsoft-Windows-Diagnosis0MSDT/Debug
  • Microsoft-Windows-Eventlog-ForwardPlugin/Debug
  • Microsoft-Windows-PrinterSpooler/Aux-Analytic
  • Microsoft-Windows-PrinterSpooler/Core-Analytic
  • Microsoft-Windows-PrinterSpooler/Core-Debug
  • Microsoft-Windows-PrinterSpooler/ISV-Analytic
  • Microsoft-Windows-Security-Licensing-SLC/Per

On a fresh install Win7 machine, those logs are definitely not there. I haven't figured out how to edit the default query, if anyone knows I'd love to get this fixed.

Finally, as I find the Windows Media Player far too weird to use, I ran into my first Win7 application compatibility problem with Winamp. It would report some odd error in the skinning code and die. A quick search around the internet showed that the problem was the default "Bento" skin and deleting the C:\Users\<USER>\AppData\Roaming\Winamp directory allows you to reset the skin to "Modern" and it works. I've never quite understood why these music and video players are so problematic for both usability and stability.

I consider the upgrade install a success because I'm loving Win 7 on a big dual monitor machine. Did anyone else do an upgrade install? If so, how did it go for you?

Thanks for all the comments and emails about my blog entry, PDB Files: What Every Developer Must Know. There was lots of nice feedback, but more importantly great questions! Instead of responding in the blog comments to these questions as I normally do, a couple of the questions were important enough that I wanted to make full blog entries out of them. Please continue to ask any questions you have about PDB files as I am taking it as my personal mission to ensure that everyone developing for Windows knows exactly how to use PDB files!

The first question I want to cover is from Eric Hill:

Thanks for the deep dive on PDB files. You're exactly right, all the information you have provided probably exists somewhere, but not all together.

I suspect the information you've provided explains a problem I had recently doing remote debugging I copied my private build to another machine, with PDB file right there with it, but when I remote-debugged, VS could not find any of my breakpoints. Just on a hunch, I moved the executable files on the remote machine such the path was the same as where the files were on the build machine, and that fixed the problem. But that doesn't seem like a very reasonable requirement. What approach would you take to remove this restriction for remote debugging?

For the most part, remote debugging works well. However, the one thing I haven't found discussed in the Remote Debugging documentation is where the PDB files are loaded, which is the crux of Eric's issue.(For the rest of this article, I'm assuming you've read all the remote debugging documentation.) To make everything clear, let me define two terms; the remote machine is where MSVSMON.EXE and your application execute and the local machine is where the Visual Studio IDE is running. The key trick to know about remote debugging is for .NET binaries, the PDB files are loaded on the remote machine and for native binaries, the PDB files are loaded on the local machine.

From Eric's question, I'm betting he was doing native remote debugging because when he moved the binaries to the same path on both machines, everything worked like expected because it was Visual Studio found the binary on the local machine and in turn, the PDB file as I described in my previous blog entry.

If you want to verify where the PDB files load, here's how I verified the loading. Set up remote debugging for a console application using your favorite language. Put the project in the same drive and directory on both the local and remote machine. Start single stepping with Visual Studio and stop at the entry point of the application. Run Process Explorer on both machines and in each press CTRL+F to search for handles and DLL strings. Enter "PDB" and click the Search button. Here's a screen shot from the local machine where I was remote debugging to a native C++ application:

With Visual Studio doing the symbol loading for native remote debugging, both local build and public build symbols load where you expect them to load. It's a little more interesting for .NET binaries for remote debugging. For your private builds, you'll copy the binary and matching PDB file to the remote machine where the loading occurs. What about the public build PDB files for .NET binaries?

As all .NET PDB files are loaded on the remote machine, the public build PDB files are loaded there as well. In order to get them you have to set the _NT_SYMBOL_PATH environment variable to point to your symbol server. If you don't want to do that manually for forty test systems, you can use the PowerShell script I wrote to automate the process. Once you have the _NT_SYMBOL_PATH set on the remote machine, when you start remote debugging, Visual Studio will let you know that it will be downloading public symbols to the remote machine:

 

With remote debugging, if both machines have the local build binaries in the same drive and directories, life is easy because the symbol loading occurs without you having to worry about the type of development you're doing. Eric asks if that's a reasonable requirement. Well, I don't have a real answer other than to say that's just kind of how it is, especially for native code. I wanted to provide the background as to how remote debugging worked so you would understand the limitations and ramifications.

Please keep the questions about PDB files coming!

Personal note: I have to let everyone know that Eric Hill, who asked the above question, had a huge influence on my career. Way back in 1992/1993 he was punished for some horrible past life transgression by being stuck with me as a lab partner for CSC 311 (Computer Organization and Logic) at North Carolina State University. That was the killer death class in the CS curriculum that everyone dreaded as it was electrical engineering for CS majors. Back in those days, you were handed a breadboard, an Intel 8051CPU, a bunch of wires and you truly built a computer, none of that easy simulation garbage they have today. Eric's brilliant (he got an A+) and pulled my stupid butt through the whole class (and I was thrilled with my C-). Oh, he also kept me from electrocuting myself a couple of times as well. When it came time for the end of year project, where we had to do something real, like control hardware devices, with our hand built computer we didn't want to have to unplug all those wires we had carefully gotten working. Eric had the brilliant idea of doing a PROM-based debugger as our project. It was all software and much easier than anything else we could have done. As you can guess, all my interest in debuggers stem from that wonderful project. Thanks, Eric!

As I have written in the past, I have a bit of a "love-hate" relationship with Lenovo and their ThinkPad line, but its way more on the love side because their hardware utterly rocks. As it was getting time for a new laptop, I surfed over to the Lenovo site at on April 28 and went through the customized build process to build my ultimate X200 Tablet. I'd checked out the new machines a couple of months ago, but noticed a few interesting things they'd added.

The first was they were now offering a 2.13 GHz L9600 Core Duo with a 6MB L2 Cache as a CPU option. While not a huge speed bump over the 1.86 GHz, every little bit helps. I configured the machine maxing out everything with 4GB RAM, touch screen display, AT&T mobile broadband, fingerprint reader, camera, Bluetooth, 8-cell battery, and the 128GB Solid State Drive (SSD). (A guy can always dream, right?) Anyway, I was sure the machine I configured was going to be too expensive to buy. That's when I noticed something very interesting, the SSD price was wrong with the result that the SSD drive was free!

There was no way that could be correct, but I thought it couldn't hurt to go through with the order. The front page of the Lenovo site had mentioned an e-coupon code that if you ordered by the end of the April, you got 15% off. The configuration computer price never changed through the end of the order and I duly got the 15% discount. Feeling lucky, but sure there was no way I was going to get a free SSD drive, I submitted my order.

The next day I called Lenovo to find out the real cost of the computer. The customer service person quoted me the $1,800 (minus tax) that I had from my web site order confirmation. When I pointed out that SSD pricing was wrong, the representative said: "I guess it's your lucky day because that's the price we are charging you. In fact, I didn't know we sold 2.13 GHz CPUs in that computer either." That sent me scrambling back to the Lenovo web site and it no longer showed the 2.13 GHz CPU for the X200 Tablet and the SSD price error was fixed.

I'd managed to order a model that didn't exist and was far underpriced so I wasn't sure what was going to happen. The ship date was originally May 8, and as I expected, on May 8 the ship date changed to May 14. Now I was curious to see what was going to happen so I waited until May 14, mainly because I was in China, thinking that there was going to be further delays because of the messed up order. My plan was to call Lenovo and get everything straightened out by opting for the 1.86 GHz CPU and a normal hard disk as my current order was probably going to be stuck in their system until they actually released the version with the 2.13 GHz CPU many months from now. On May 14, my order status changed to "shipped" and had a tracking number, which was quite a surprise.

On May 19, UPS delivered the computer. The specifications sticker on the box said it had a 2.13 GHz CPU and firing it up reported two important facts, first it did contain the 2.13 GHz CPU, and second, it definitely contained a blazingly fast SSD drive! Looking at the price of the maxed out X200 Tablet as I write this shows the price to be $2,900 (with no coupons applied) and still does not offer the 2.13 GHz CPU as an option. There's nothing like getting a smoking hot computer for nearly half off!

I was expecting Lenovo to call me and ask me to pay for the SSD drive before shipping the computer, which I would have completely understood and gladly have done, but they never did. I hope they will accept my full gratitude for the deal, and even more devotion from me in exchange. While you probably won't get the deal I got, if you need a rock solid laptop you can trust, go with a Lenovo. I've been using them for 15 years and loving them. OK, there was that two-year flirtation with a Toshiba M200 that I got free, but I never liked it and felt dirty the whole time.

After verifying that everything worked on the machine, it was time for Win7 x64. As I wanted to control the machine, I wiped out everything Lenovo had installed and started fresh. The Win7 RC install off a bootable USB memory drive was trivial. If others with X200 Tablets are looking to go to Win7 x64, which I would highly recommend, here's all I installed. All the rest of the drivers I let Windows 7 or Windows Update install for me.

Two things that aren't working for me are the Intel AMT serial drivers and the fingerprint reader. I don't really care about the AMT part, but I tried to get the Lenovo Vista fingerprint reader software to work, but there are too many changes between Vista and Win7 for biometric devices in order for them to work.

The big thing I've learned with the X200 Tablet is that I will never use a platter hard disk again. I'd read that an SSD makes a big difference, until I used one the last couple of days for real world use, I didn't realize just how much faster they are. Another feature is the silent operation. I guess I had gotten used to the hard drive noise, but now that it's gone on the new machine, it's annoying to use a regular computer again.

While I have taken a Surface development class, I never used the touch features in Win7. Now that I have, I don't know how I ever lived without them! With the taskbar in particular, it's so convenient and faster to have your hands on the keyboard and reach up to touch the application to switch to, especially if you run as many as I do. After two days of using my new laptop, I went to my old laptop and couldn't understand why I couldn't switch applications any more when I touched the screen.

While you're probably already sick of this blog entry because of my killer deal, I do have one last thing to brag about, battery life. I have the 8-cell battery in this machine and the other night I worked six solid hours and still had 20% of the battery left when I put the machine back in the UltraBase. Between Win7 and better hardware, it's now realistic to get real development work done when you fly across the US.

Most developers realize that PDB files are something that help you debug, but that's about it. Don't feel bad if you don't know what's going on with PDB files because while there is documentation out there, it's scattered around and much of it is for compiler and debugger writers. While it's extremely cool and interesting to write compilers and debuggers, that's probably not your job.

What I want to do here is to put in one place what everyone doing development on a Microsoft operating system has to know when it comes to PDB files. This information also applies to both native and managed developers, though I will mention a trick specific to managed developers. I'll start by talking about PDB file storage as well as the contents. Since the debugger uses the PDB files, I'll discuss exactly how the debugger finds the right PDB file for your binary. Finally, I'll talk about how the debugger looks for the source files when debugging and show you a favorite trick related to how the debugger finds source code.

Before we jump in, I need to define two important terms. A build you do on your development machine is a private build. A build done on a build machine is a public build. This is an important distinction because debugging binaries you build locally is easy, it is always the public builds that cause problems.

The most important thing all developers need to know: PDB files are as important as source code! Yes, that's red and bold on purpose. I've been to countless companies to help them debug those bugs costing hundreds of thousands of dollars and nobody can find the PDB files for the build running on a production server. Without the matching PDB files you just made your debugging challenge nearly impossible. With a huge amount of effort, my fellow Wintellectuals and I can find the problems without the right PDB files, but it will save you a lot of money if you have the right PDB files in the first place.

As John Cunningham, the development manager for all things diagnostics on Visual Studio, said at the 2008 PDC, "Love, hold, and protect your PDBs." At a minimum, every development shop must set up a Symbol Server. I've written about Symbol Servers in MSDN Magazine and more extensively in my book, Debugging .NET 2.0 Applications. You can also read the Symbol Server documentation itself in the Debugging Tools for Windows help file. Look at those resources to learn more about the details. Briefly, a Symbol Server stores the PDBs and binaries for all your public builds. That way no matter what build someone reports a crash or problem, you have the exact matching PDB file for that public build the debugger can access. Both Visual Studio and WinDBG know how to access Symbol Servers and if the binary is from a public build, the debugger will get the matching PDB file automatically.

Most of you reading this will also need to do one preparatory step before putting your PDB files in the Symbol Server. That step is to run the Source Server tools across your public PDB files, which is called source indexing. The indexing embeds the version control commands to pull the exact source file used in that particular public build. Thus, when you are debugging that public build you never have to worry about finding the source file for that build. If you're a one or two person team, you can sometimes live without the Source Server step. For the rest of you, read my article in MSDN Magazine on Source Server to learn how to use it.

The rest of this entry will assume you have set up Symbol Server and Source Server indexing. One good piece of news for those of you who will be using TFS 2010, out of the box the Build server will have the build task for Source Indexing and Symbol Server copying as part of your build.

One complaint I've heard against setting up a Symbol Server from some teams is that their software is too big and complex. I have to admit that when I hear people say that it translates to me as "My team is dysfunctional." There's no way your software is bigger and more complex than everything Microsoft does. They source index and store every single build of all products they ship into a Symbol Server. That means everything from Windows, to Office, to SQL, to Games and everything in between is stored in one central location. My guess is that Building 34 in Redmond is nothing but SAN drives to hold all of those files and everyone in that building is there to support those SANs. It's so amazing to be able to debug anything inside Microsoft and you never have to worry about symbols or source (provided you have appropriate rights to that source tree).

With the key infrastructure discussion out of the way, let me turn to what's in a PDB and how the debugger finds them. The actual file format of a PDB file is a closely guarded secret but Microsoft provides APIs to return the data for debuggers. A native C++ PDB file contains quite a bit of information:

  • Public, private, and static function addresses
  • Global variable names and addresses
  • Parameter and local variable names and offsets where to find them on the stack
  • Type data consisting of class, structure, and data definitions
  • Frame Pointer Omission (FPO) data, which is the key to native stack walking on x86
  • Source file names and their lines

A .NET PDB only contains two pieces of information, the source file names and their lines and the local variable names. All the other information is already in the .NET metadata so there is no need to duplicate the same information in a PDB file.

When you load a module into the process address space, the debugger uses two pieces of information to find the matching PDB file. The first is obviously the name of the file. If you load ZZZ.DLL, the debugger looks for ZZZ.PDB. The extremely important part is how the debugger knows this is the exact matching PDB file for this binary. That's done through a GUID that's embedded in both the PDB file and the binary. If the GUID does not match, you certainly won't debug the module at the source code level.

The .NET compiler, and for native the linker, puts this GUID into the binary and PDB. Since the act of compiling creates this GUID, stop and think about this for a moment. If you have yesterday's build and did not save the PDB file will you ever be able to debug the binary again? No! This is why it is so critical to save your PDB files for every build. Because I know you're thinking it, I'll go ahead and answer the question already forming in your mind: no, there's no way to change the GUID.

However, you can look at the GUID value in your binary. Using a command line tool that comes with Visual Studio, DUMPBIN, you can list all the pieces of your Portable Executable (PE) files. To run DUMPBIN, open the Visual Studio 2008 Command Prompt from the Program's menu, as you will need the PATH environment variable set in order to find the DUMPBIN EXE. By the way, if you're interested in more about the information that DUMPBIN shows you, I highly recommend the definitive articles on the PE file by Matt Pietrek in the February 2002 and March 2002 issues of MSDN Magazine.

There are numerous command line options to DUMPBIN, but the one that shows us the build GUID is /HEADERS. The Pietrek articles will explain the output, but the important piece to us is the Debug Directories output:

Debug Directories
Time Type Size RVA Pointer
-------- ------ -------- -------- --------
4A03CA66 cv 4A 000025C4 7C4 Format: RSDS,
  {4B46C704-B6DE-44B2-B8F5-A200A7E541B0}, 1,
C:\junk\stuff\HelloWorld\obj\Debug\HelloWorld.pdb

With the knowledge of how the debugger determines the correctly matching PDB file, I want to talk about where the debugger looks for the PDB files. You can see all of this order loading yourself by looking at the Visual Studio Modules window, Symbol File column when debugging. The first place searched is the directory where the binary was loaded. If the PDB file is not there, the second place the debugger looks is the hard coded build directory embedded in the Debug Directories in the PE file. If you look at the above output, you see the full path C:\JUNK\STUFF\HELLOWORLD\OBJ\DEBUG\HELLOWORD.PDB. (The MSBUILD tasks for building .NET applications actually build to the OBJ\<CONFIG> directory and copy the output to DEBUG or RELEASE directory only on a successful build.) If the PDB file is not in the first two locations, and a Symbol Server is set up for the on the machine, the debugger looks in the Symbol Server cache directory. Finally, if the debugger does not find the PDB file in the Symbol Server cache directory, it looks in the Symbol Server itself. This search order is why your local builds and public build parts never conflict.

How the debugger searches for PDB files works just fine for nearly all the applications you'll develop. Where PDB file loading gets a little more interesting are those .NET applications that require you to put assemblies in the Global Assembly Cache (GAC). I'm specifically looking at you SharePoint and the cruelty you inflict on web parts, but there are others. For private builds on your local machine, life is easy because the debugger will find the PDB file in the build directory as I described above. The pain starts when you need to debug or test a private build on another machine.

On the other machine, what I've seen numerous developers do after using GACUTIL to put the assembly into the GAC is to open up a command window and dig around in C:\WINDOWS\ASSEMBLY\ to look for the physical location of the assembly on disk. While it is subject to change in the future, an assembly compiled for Any CPU is actually in a directory like the following:

C:\Windows\assembly\GAC_MSIL\Example\1.0.0.0__682bc775ff82796a

Example is the name of the assembly, 1.0.0.0 is the version number, and 682bc775ff82796a is the public key token value. Once you've deduced the actual directory, you can copy the PDB file to that directory and the debugger will load it.

If you're feeling a little queasy right now about digging through the GAC like this, you should, as it is unsupported and fragile. There's a better way that seems like almost no one knows about, DEVPATH. The idea is that you can set a couple of settings in .NET and it will add a directory you specify to the GAC so you just need to toss the assembly and it's PDB file into that directory so debugging is far easier. Only set up DEVPATH on development machines because any files stored in the specified directory are not version checked as they are in the real GAC.

By the way, if you search for DEVPATH in any internet search engine one of the top entries is an out of date blog entry by Suzanne Cook saying Microsoft was getting rid of DEVPATH. That is no longer true. As with any blog entry, look at the date on Suzanne's blog: 2003. That's the equivalent of 1670 in internet years.

To use DEVPATH, you will first create a directory that has read access rights for all accounts and at least write access for your development account. This directory can be anywhere on the machine. The second step is to set a system wide environment variable, DEVPATH whose value is the directory you created. The documentation on DEVPATH doesn't make this clear, but set the DEVPATH environment variable before you do the next step.

To tell the .NET runtime that you have DEVPATH set up requires you to add the following to your APP.CONFIG, WEB.CONFIG, or MACHINE.CONFIG as appropriate for your application:

<configuration>
   <runtime>
      <developmentMode developerInstallation="true"/>
   </runtime>
</configuration>

Once you turn on development mode, you'll know there's a problem with either the DEVPATH environment variable missing for the process or the path you set does not exist if your application dies at startup with a COMException with the error message saying the completely non-intuitive: "Invalid value for registry." Also, be extremely vigilant if you do want to use DEVPATH in MACHINE.CONFIG because every process on the machine is affected. Causing all .NET applications to fail on a machine won't win you many friends around the office.

The final item every developer needs to know about PDB files is how the source file information is stored in a PDB file. For public builds that have had source indexing tools run on them, the storage is the version control command to get that source file into the source cache you set. For private builds, what's stored is the full path to the source files that compiler used to make the binary. In other words, if you use a source file MYCODE.CPP in C:\FOO, what's embedded in the PDB file is C:\FOO\MYCODE.CPP. This is probably what you already suspected, but I just wanted to make it clear.

Ideally, all public builds are automatically being source indexed immediately and stored in your Symbol Server so if you don't have to even think any more about where the source code is. However, some teams don't do the source indexing across the PDB files until they have done smoke tests or other blessings to see if the build is good enough for others to use. That's a perfectly reasonable approach, but if you do have to debug the build before its source indexed, you had better pull that source code to the exact same drive and directory structure the build machine used or you may have some trouble debugging at the source code level. While both the Visual Studio debugger and WinDBG have options for setting the source search directories, I've found it hard to get right.

For smaller projects, it's no problem because there's always plenty of room for your source code. Where life is more difficult is on bigger projects. What are you going to do if you have 30 MB of source code and you have only 20 MB of disk space left on your C: drive? Wouldn't it be nice to have a way to control the path stored in the PDB file?

While we can't edit the PDB files, there's an easy trick to controlling the paths put inside the PDB files: SUBST.EXE. What SUBST does is associate a path with a drive letter. If you pull your source code down to C:\DEV and you execute "SUBST R: C:\DEV" the R: drive will now show at its top level the same files and directories if you typed "DIR C:\DEV." You'll also see the R: drive in Explorer as a new drive. You can also achieve the drive to path affect by mapping a drive to a shared directory in Explorer. I personally prefer the SUBST approach because it doesn't require any shares on the machine. While some of you are thinking that you can share through <DRIVE>$, some organizations disable that functionality.

What you'll do on the build machine is set a startup item that executes your particular SUBST command. When the build system account logs in, it will have the new drive letter available and that's where you'll do your builds. With complete control over the drive and root embedded in the PDB file, all you need to do to set up the source code on a test machine is to pull it down wherever you want and do a SUBST execution using the same drive letter the build machine used. Now there's no more thinking about source matching again in the debugger.

While not all of this information about PDB files I've discussed in this entry is entirely new, I didn't see it in one place before. I hope by getting it all together that you'll find it easier to deal with what's going on and debug your applications faster. Debugging faster means shipping faster so that's always high on the good things scale. Please ask any questions you may have on PDB files in comments, and I'll be happy to dig up the answers for you.

As I've written in the past, I've been using VMware (both Workstation 6.5 and Server 1.04) for what seems like ages. They've served me well but I wanted to be prepared for two changes coming up this year. The first is Server 2008 R2, which is 64-bit only and secondly Visual Studio Team Foundation System 2010, which I'm guessing will be distributed in a Hyper-V ready image. While I was perfectly happy with VMware Server, my old partner in crime from my NuMega days, Matt Pietrek, is on the Hyper-V team so was giving me heck for not supporting him. How could I resist that pressure?

The transition was generally painless but I thought I'd share a few of the notes I took as I got everything working in the hopes that someone will find them useful. My domain is nothing too fancy just four virtual machines: a domain controller, a TFS 2008 server, a file server, and a build machine. On the user side there's me and "She who must be obeyed" (AKA my wife). My server is a wonderful Mac Pro with 9GB RAM with 1.6 TB of drives. When running VMware Server, the host machine was running Server 2003 R2 x64 and all the virtual machines were 32-bit Server 2003 instances. My plan was to move the 32-bit TFS server over from VMware directly and replace all the other servers with the x64 versions of Server 2008 so when Server 2008 R2 ships later this year, I'm all ready for it.

As I have VMware Workstation on my main development machine (another Mac Pro), I moved the .VMDK and .VMX files for both my domain controller and file server to run them on my desktop while working on this transition. One key point if you do the same: remember to set your power plan on the desktop to keep the machine awake and not go to sleep. Having your most important client yelling at you because the mail and web are down is quite confusing when you're in the middle of watching the Server 2008 installation copy files and you know you haven't touched anything yet.

Right at the beginning, I ran into a Mac Pro specific obstacle, as I couldn't boot in the Windows 2008 install. The boot would initiate on the CD and the following came up on the monitor:

1.
2.
Select CD-ROM Boot Type:

 

No amount of pressing 1 or 2 on the keyboard would work. Fortunately, others had already blazed this trail and following Jowie's steps got the installation working for me.

Once I got Hyper-V installed, I did some experiments with dynamic and fixed .VHD disks. As I had done with VMware, I wanted to have an emergency XCOPY backup of the files for the data files. While not officially supported by Microsoft, being able to restore a machine with a copy command can be a lifesaver. I did both a dynamic and fixed disk test with the excellent DISKSHADOW program from Server 2008 to invoke the Hyper-V VSS writer without shutting down the running VM. As I suspected restoring the dynamic disk copy caused the incomplete shutdown text screen to show up when restarted. The fixed disk restore worked cleanly so that made up my mind that I was going with fixed disks for everything. The trade off is that a fixed disk of 40GB means you get a file of 40GB, but disk space is nearly free these days.

In reading about the supported backup methods for Hyper-V servers, I was disappointed that it's all or nothing. That's primarily because the backup program in Server 2008 takes a snapshot of the whole volume so if you want to restore a single VM on that disk volume you can't because it's an all or nothing affair. What I decided to do was partition up my disks and only run one VM on each partition so I could get the supported backup scenario and only have to restore the one VM that needs restoring.

I have an excellent HP Home Server doing my client backups and it's truly a "set and forget" backup system. While some have reported that WHS can backup Server 2008, I haven't seen where anyone explains the exact step for a valid restore. Maybe I can become an internet hero if I take the plunge and figure out all the steps. I'll put it on my "to-do" list.

Implementing the new domain controller was a piece of cake with Server 2008. I'm glad I did it now, as there is still a 32-bit version of Server 2008 because running ADPREP before introducing the new domain controller makes life much easier. Not being much of an Active Directory person I'm always terrified when messing around with domain controllers because reading TechNet makes it sound like AD is the most fragile thing in the world. I guess that's my inexperience talking but I don't think I'm alone. One major trick I do know is that before you demote a domain controller, always run the following command:

netdom query fsmo

That way you make sure you don't accidentally leave those all-important FSMO roles floating out there. I don't know if DCPROMO will catch that error, but I wouldn't want to find out because then you'll have to seize them, which sound quite unpleasant. For you AD novices like me that are looking for AD resources for normal people Sander Berkouwer's excellent blog is a treasure.

Installing MS-DOS 2008, oh, I mean Server Core 2008, for my file server was easy. The problem started when I couldn't log in the first time. Who knew a blank password for Administrator was the way to log on? Another bit of feedback I have for the team is it would be a great idea to have a text file with all the configuration commands in it, like Greg Shield's article, as part of the install so you could open it in NOTEPAD for easy cut and paste to the command window

With the three x64 servers running great I turned to converting my 32-bit Team System .VMDK, which is easy with the free VMDK to VHD Converter from the fine folks at vmToolkit. A little poking around the web got me to a blog entry from Ken Schaefer where he discussed his experiences moving from VMware to Hyper-V. He reported there were troubles starting a converted VMware system on Hyper-V RC0 because VMware is using SCSI disks and Hyper-V needs an IDE disk in order to boot. The work around was simply adding an uninitialized IDE disk to the VMware instance before shutting it down. Not finding any other information anywhere, I figured it couldn't hurt to add the IDE drive because I didn't want to go through a repair installation to get things working again. Before I did the conversion, I also removed the VMware tools from my TFS machine. After I did the conversion from VMware to Hyper-V, which was to a dynamic disk, I made the new .VHD a fixed disk with Hyper-V.

The first boot of the converted .VHD went well but had a message box that a service had failed to start. The Event Viewer showed it to be VMMEMCTL. As that's a VMware service that looks like it didn't properly uninstall I manually deleted all the references with REGEDIT, and the machine was fine. After installing the Hyper-V VM Guest services, which gave me the network connection, I had to authenticate the computer, which I expected since all the hardware changed on the machine.

After the TFS server was up and running, I noticed it was running very slowly. In VMware, I had only allocated 1 GB RAM to the machine, which was sufficient. Logging into the machine showed the memory usage was nearly maxed out and it was swapping like crazy. I shut it down and reconfigured Hyper-V to give it 2 GB RAM to see if that would help out, which it did. What's different is that SQL Server 2005 is now using 700MB of memory for its working set. I'm not sure what's causing the difference as everything's running great and there's no SQL messages in the event log. In all, it was cool that the conversion was that easy.

With everything running great on Hyper-V, it was time to tackle my XCOPY backups and that's where I ran into a big problem. I created a DISKSHADOW script for one of my VMs and ran it. Here's the output I got:

DISKSHADOW> delete shadows all
No shadow copies found in system.
DISKSHADOW> set context persistent
DISKSHADOW> Writer Verify {66841cd4-6ded-4f4b-8f17-fd23f8ddc3de}
DISKSHADOW> add volume c: alias myshadow create
The component "Microsoft Hyper-V VSS Writer" was not found in the writer components list. Aborting backup ...
Check the syntax of the component name.

That was odd that the Hyper-V VSS writer was no longer there. Running the DISKSHADOW list writers command showed the Hyper-V VSS writer active. Running VSSADMIN with the list writers command, which shows you the state of the VSS writers showed life was peachy:

vssadmin 1.1 - Volume Shadow Copy Service administrative command-line tool
(C) Copyright 2001-2005 Microsoft Corp.
Writer name: 'Microsoft Hyper-V VSS Writer'
   Writer Id: {66841cd4-6ded-4f4b-8f17-fd23f8ddc3de}
   Writer Instance Id: {a61296b9-d85f-4400-9bfb-ea58b2b78294}
   State: [1] Stable
   Last error: No error

The event log showed an odd VSS error 1009 with no information:

The description for Event ID 10009 from source VSS cannot be found. Either the component that raises this event is not installed on your local computer or the installation is corrupted. You can install or repair the component on the local computer.

If the event originated on another computer, the display information had to be saved with the event.

The following information was included with the event:


the message resource is present but the message is not found in the string/message table

Using the Server 2008 backup system from the command line, I was able to back up the entire machine with no problems. Here's where I was perplexed because it sure looked like no matter what I tried, DISKSHADOW would not back up my VMs! I really want that XCOPY save your butt backup!

In panic, I wrote a friend at Microsoft and after a little back and forth, we figured out the problem. Part of it was between the keyboard and chair in my office and part of it was a completely bogus error message. Where I was thinking DISKSHADOW worked on an individual disk, it really needs to be named MACHINESHADOW. It will only work if you include all the component volumes listed under the Hyper-V VSS Writer when you do the list writers command. As I have four drives with running VHDs on them, I need to ensure the following as in the DISKSHADOW script I use:

Add Volume E:\ Alias DomainControllerShadow
Add Volume D:\ Alias FileServerShadow
Add Volume I:\ Alias TeamSystemShadow
Add Volume J:\ Alias BuildMachine
Add Volume C:\ Alias Ignored

Even though I don't have any VMs running on my C: drive if it's in the component list, it has to be included. Given the bizarre error messages in the output and the event log, I hope this helps someone else if they run into the same problem.

After hurtling my small bump with DISKSHADOW, it was neat to see VMs backed up without shutting them down. I'm including my domain controller as part of that backup. The whole snapshot and ROBOCOPY process for all four VMs takes about 40-45 minutes total as I first save everything on the HYPER-V machine. The ROBOCOPY of those saved images to my WHS machine is about 30 minutes.

My small environment doesn't have any replication nor am I replying on snapshots so it works for me (YMMV). Of course, I'm still doing normal backups in addition. If your network is more complicated than mine is you must read the great whitepaper Running Domain Controllers in Hyper-V so you don't cause yourself any problems.

If anyone has any questions about what I've done please ask away.

Missed Devscovery NYC? See the Keynote!

If you couldn't make it to NYC last week to have a blast at Devscovery, we missed you. One of the most interesting parts was Scott Hanselman's keynote on Social Networking for Developers. Scott, with the help of Paul Mooney, have posted the video so you can watch the keynote and see how things like blogs, Twitter, and Facebook can make you a better developer. It's a fantastic talk and will give everyone something to think about.

After the conference, I looked over the reviews and Scott's keynote got rave reviews. His keynote is full of practical advice if you've never dipped your toe into the blogging or social networking world. Even if you're on everything, there are many ideas on how you can use social networking to "manage your brand." It today's economic climate, it's critical that you do.

Several of the people I talked to at Devscovery were inspired to start their own blogs because of Scott's talk but were worried that they didn't have anything to say. Everyone developing software for a living has plenty to say that others want to read about. You're fighting battles everyday to get the job done and make a difference. We all want to read about those battles so we can avoid the same problems ourselves.

Also, look at it this way, as a software developer we are really in the communications business. The more you practice your written communications skills, the better you become. It doesn't take that much time to write, maybe 15-20 minutes a day. Just do it!

Thanks again to Scott for doing an amazing job. If you want to see Scott live, which is not to be missed, there's always Devscovery Redmond coming up August 18-20.

In the last two weeks I've gotten many questions about how to automate and program the exception handling the Visual Studio debugger so I thought I'd show how to some cool macros I've created to make it easy. If you're interested in more about making the most of the debugger you can always come to our Devscovery conference in New York City starting on April 14th. (Bethany, Wintellect's marketing maestro, is going to love me for working in the advertisement there!).

All the questions I got about the debugger exception handling fell into two buckets. The first was that teams have custom exceptions that they want to add to the Exceptions dialog and don't want to have to add those manually every time the move to a new machine. Since the Exception settings are stored in the .SUO file next to the solution, you also lose those exceptions if you delete the .SUO file. The second question was about programmatically setting just a handful of exceptions to stop when thrown. What was interesting about the second question is that the people asking had the neat idea of having their tests running under the Visual Studio debugger and configured to ignore all exceptions but a handful. That way they'd have those tough error conditions already in the debugger in order to make their debugging easier. I thought that was a very interesting idea.

As with most things in life, there's some good news and bad news about programmatically manipulating exceptions through the Visual Studio automation model. The good news is that the automation model has everything you need. The bad news is that certain operations, like setting all exceptions to stop when thrown, if done with a macro as so abysmally slow that it's essentially unusable. I suspect the performance would be better if I wrote an add-in. I'm hoping the VS 2010 will improve the performance of macros so more people will consider writing quick extensions to the IDE.

Let me start by showing you a simple macro from Jim Griesmer that sets a CLR exception to stop when thrown:

Sub BreakWhenThrown(Optional ByVal strException As String = "")
    
Dim dbg As Debugger3 = DTE.Debugger
    
Dim eg As ExceptionSettings = _
        dbg.ExceptionGroups.Item(
"Common Language Runtime Exceptions")
    eg.SetBreakWhenThrown(
True, eg.Item(strException))
End Sub

To execute the above macro, you'll open the Command window and pass the full name of the exception on the command line like the following:

>Macros.MyMacros.CLRExceptions.BreakWhenThrown System.ArgumentException

The macro itself if relatively straightforward. Once you have the Debugger3 interface, you can get the exceptions under a category by name. As you probably guessed the names of the exception groups maps to exactly what you see in the Exceptions dialog in Visual Studio.

Note that my Exception dialog probably looks different than yours because I turned off Just My Code in the Visual Studio Options dialog (Options, Debugging, General). I highly recommend you do as well because having Just My Code turned on turns off very valuable features such as debugging optimized builds and the awesome .NET Reference Source Code debugging.

The real work in the macro is all in the ExceptionsGroup interface as it has the methods on it to set, create, and remove individual exceptions. To get an individual exception, you access the ExceptionsGroup Items collection by exception name to get the ExceptionSetting.

At the bottom of this blog entry, I included the macro source code for a set of macros that wrap up adding, removing, and configuring CLR exceptions easy with full error handling and reporting. For those of you doing native development, you'll get the idea what you need to do. The one difference with Win32 Exceptions verses the other categories of exceptions is that you'll need to specify the exception codes to those exceptions.

In the code I wanted to include macros that would let me set or clear stopping when exceptions were thrown. The problem was that the macro literally took more than 15 minutes to enumerate and set each exception setting. It's faster to manually bring up the Exceptions dialog and click the check box next to the category. I'll keep looking to see if I can find a faster way to enable and disable stopping when thrown.

Those of you using my Debugger Settings add in I'm working on adding support for exception settings to it. Keep reading this space for updates. I'm worried about the performance of saving and restoring the exception settings given the horrible performance I'm seeing from the macro so it may turn out I won't add it.

The team that was running their tests under the debugger and wanted to automate setting various exceptions also was looking for a way to programmatically execute a macro in Visual Studio. They wanted to be able to configure their special exceptions as well as automatically attach or start their application. Fortunately, that's easy to do with the /command command line option to DEVENV.EXE. That will start the IDE and execute a Visual Studio command or macro.

As always, let me know if you have any questions or have an idea you'd like to see explored.

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' CLRExceptions - John Robbins (c) 2009 - john@wintellect.com
' Macros that make it easier to manipulate CLR exceptions in the debugger.
'
' Do whatever you want with this code.
'
' Version 1.0 - April 1, 2009
'   - Initial version.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90
Imports System.Diagnostics
Imports System.Text
Imports System.Collections.Generic
Imports System.Runtime.InteropServices

Public Module CLRExceptions

    
' Adds a new exception name to the list of CLR exceptions.
    Sub CLRExceptionCreate(Optional ByVal strException As String = "")
        CreateDeleteCLRException(strException,
True)
    
End Sub

    ' Removes an exception from the list of CLR exceptions.
    Sub CLRExceptionDelete(Optional ByVal strException As String = "")
        CreateDeleteCLRException(strException,
False)
    
End Sub

    ' Sets the specifically named exception to stop when thrown.
    Sub CLRExceptionBreakWhenThrown(Optional ByVal strException As String = "")
        SetClrException(strException,
True)
    
End Sub

    ' Sets the specifically named exception to continue when thrown.
    Sub CLRExceptionContinueWhenThrown(Optional ByVal strException _
                                      
As String = "")
        SetClrException(strException,
False)
    
End Sub

    ' Helper method to create or delete a CLR exception.
    Private Sub CreateDeleteCLRException(ByVal strException As String, _
                                        
ByVal create As Boolean)
        
Dim dbg As Debugger3 = DTE.Debugger
        
Dim cmdWindow As CmdWindow = New CmdWindow()
        
If (True = String.IsNullOrEmpty(strException)) Then
            cmdWindow.WriteLine("You must pass the exception as the parameter")
            
Return
        End If

        ' If ExceptionGroups is null, there's no project loaded.
        If (dbg.ExceptionGroups IsNot Nothing) Then

            Dim eg As ExceptionSettings = _
                dbg.ExceptionGroups.Item(
"Common Language Runtime Exceptions")
            
Try
                If (True = create) Then
                    eg.NewException(strException, 100)
                    cmdWindow.WriteLine(
"New CLR exception created: " + _
                                        strException)
                
Else
                    eg.Remove(strException)
                    cmdWindow.WriteLine(
"CLR exception deleted: " + _
                                        strException)
                
End If
            Catch ex As COMException
                cmdWindow.WriteLine(ex.Message)
            
End Try
        Else
            cmdWindow.WriteLine("You must open a solution to set exceptions")
        
End If
    End Sub

    ' Helper method to have a CLR exception stop or continue when thrown.
    Private Sub SetClrException(ByVal strException As String, _
                                
ByVal enabled As Boolean)
        
Dim dbg As Debugger3 = DTE.Debugger
        
Dim cmdWindow As CmdWindow = New CmdWindow()

        
If (True = String.IsNullOrEmpty(strException)) Then
            cmdWindow.WriteLine("Missing exception parameter")
            
Return
        End If

        Dim eg As ExceptionSettings = _
                dbg.ExceptionGroups.Item(
"Common Language Runtime Exceptions")
        
' If ExceptionGroups is null, there's no project loaded.
        If (dbg.ExceptionGroups IsNot Nothing) Then
            Try
                eg.SetBreakWhenThrown(enabled, eg.Item(strException))
                
Dim msg As String = "continue"
                If (True = enabled) Then
                    msg = "break"
                End If
                cmdWindow.WriteLine("Exception '" + strException + _
                                    
"' will " + msg + " when thrown")
            
Catch ex As COMException
                cmdWindow.WriteLine(
"Problem setting exception: " + ex.Message)
            
End Try
        Else
            cmdWindow.WriteLine("You must open a solution to set exceptions")
        
End If
    End Sub
End
Module

' Drop this into a module called Utilites
Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90
Imports System.Diagnostics
Imports System.Globalization
Imports System.Text.RegularExpressions
Imports System.Collections.Generic

Public Module Utilities

     
' Stuff a new GUID into the current cursor location.
      Sub InsertGuid()
           
Dim objTextSelection As TextSelection
            objTextSelection =
CType(DTE.ActiveDocument.Selection(),  _
                                                 EnvDTE.TextSelection)
           
Dim cult As CultureInfo = _
                                     System.Threading.Thread.CurrentThread.CurrentUICulture
            objTextSelection.Text = Guid.NewGuid.ToString.ToUpper(cult)
     
End Sub

      ' My wrapper around the CommandWindow to make it easier to use.
      Public Class CmdWindow
           
' The internal Command window.
            Private m_CmdWnd As CommandWindow

           
' The constructor that simply gets the command window.
            Public Sub New()
                  m_CmdWnd =
CType(DTE.Windows.Item(EnvDTE.Constants.vsWindowKindCommandWindow).Object, CommandWindow)
           
End Sub

            ' Public method to write a line with a CRLF appended.
            Public Sub WriteLine(Optional ByVal Str As String = "")
                  m_CmdWnd.OutputString(Str + vbCrLf)
           
End Sub

            ' Public method to write a line.
            Public Sub Write(ByVal Str As String)
                  m_CmdWnd.OutputString(Str)
           
End Sub

            ' Clears the command window.
            Public Sub Clear()
                  m_CmdWnd.Clear()
           
End Sub

            ' Sends the input to the command window like you typed it.
            Public Sub SendInput(ByVal Command As String, Optional ByVal Commit As Boolean = True)
                  m_CmdWnd.SendInput(Command, Commit)
           
End Sub

      End Class
      Public Class OutputPane
           
' The output pane this class wraps.
            Private m_OutPane As OutputWindowPane
           
' The class constructor.
            Public Sub New(ByVal Name As String, _
                                
Optional ByVal ShowIt As Boolean = True)
                 
' First, get the main output window itself.
                  Dim Win As Window = _
                                      DTE.Windows.Item(EnvDTE.Constants.vsWindowKindOutput)
                 
' Show the window if I'm supposed to.
                  If (True = ShowIt) Then
                        Win.Visible = True
                  End If
                  ' Convert the window to a real output window.  
                  ' The VB way of casting!
                  Dim OutWin As OutputWindow = CType(Win.Object, OutputWindow)
                 
' Use exception handling to access this pane if it already exists.
                  Try
                        m_OutPane = OutWin.OutputWindowPanes.Item(Name)
                 
Catch e As System.Exception
                       
' This output tab doesn't exist, so create it.
                        m_OutPane = OutWin.OutputWindowPanes.Add(Name)
                 
End Try
                  ' If it's supposed to be visible, make it so.
                  If (True = ShowIt) Then
                        m_OutPane.Activate()
                 
End If
            End Sub
            ' Allows access to the value itself.
            Public ReadOnly Property OutPane() As OutputWindowPane
                 
Get
                        Return m_OutPane
                 
End Get
            End Property
            ' Wrapper.  Get the underlying object for all the rest
            ' of the OutputWindowPane methods.
            Public Sub Clear()
                  m_OutPane.Clear()
           
End Sub
            ' The functions I wanted to add.
            Public Sub WriteLine(ByVal Text As String)
                  m_OutPane.OutputString(Text + vbCrLf)
           
End Sub
            Public Sub Write(ByVal Text As String)
                  m_OutPane.OutputString(Text)
           
End Sub
      End Class

      ' SplitParameters - Splits up a parameter string passed to a macro
      ' into an array of parameters.
      Function SplitParameters(ByVal parameterString As String) As String()

           
' No sense doing anything if no parameters.
            If (0 = parameterString.Length) Then
                  Exit Function
            End If

            ' Cool regex from Michal Ash:
            ' http://regexlib.com/REDetails.aspx?regexp_id=621
            Dim initialArray As String()
            initialArray = Regex.Split(parameterString, _
                                   
",(?!(?<=(?:^|,)\s*\""(?:[^\""]|\""\""|\\\"")*,)" & _
                                   
"(?:[^\""]|\""\""|\\\"")*\""\s*(?:,|$))", _
                                    RegexOptions.Singleline)
           
' Go through and scrape off any extra whitespace on parameters.
            Dim returnArray As List(Of String) = New List(Of String)
           
Dim i As Int32
           
For i = 0 To initialArray.Length - 1
                  returnArray.Add(initialArray(i).Trim())
           
Next
            Return (returnArray.ToArray())
     
End Function

End
Module

 

If you're not running a 64-bit version of Windows, I bet that at least one of your customers is. While you should be working on porting your native applications to 64-bit, it doesn't look like that many teams have made that step. While it will take effort and testing to make the switch, you can make a small change to your 32-bit builds that will provide a big benefit when running on x64: more memory!

All you need to do is add the /LARGEADDRESSAWARE to the linker for your EXE and when your 32-bit EXE is run on a 64-bit system, it will have a 4GB address space instead of the default 2GB address space normally associated with 32-bit applications. As with any switches like this one, you will need to test your application on the 64-bit machine to ensure you don't have problems. If you are using the high bit of pointers addresses or subtracting pointers that are not from the same object, setting /LARGEADDRESSAWARE will cause a serious bag of hurt.

To turn on /LARGEADDRESSAWARE, go into your EXE project properties, Linker, System and set the Enable Large Address field:

To show you the benefit of the /LARGEADDRESSAWARE switch, I created a simple EXE application that allocated and committed 50 MB every time you clicked a button and popped up a message box when the allocation failed. Without the /LARGEADDRESSAWARE switch set, here's what Process Explorer reported for Private Bytes and Working Set when running on Vista x64:

After turning on the /LARGEADDRESSAWARE, here's how much memory the application was able to use on x64:

If you're curious, the exact same EXE with /LARGEADDRESSAWARE set runs perfectly fine on normal 32-bit machines. For those of you wondering what affect this has with the /3GB boot switch for the operating system, read more about the /3GB here.

While some people are taking /LARGEADDRESSAWARE to interesting extremes, it's definitely something that anyone building 32-bit EXEs should be looking at. However, as I said before, don't just flip it on and ship, you do need to test on x64 machines to ensure you or any libraries you use don't do stupid things with pointer addresses.

Whenever I teach a debugging class, the first thing I talk about is one of the most important points I make though the entire class: read Steve McConnell's masterpiece, Code Complete 2nd Edition. I ask for an honest show of hands as to who hasn't read Code Complete, and it always amazes me how many developers haven't. It's the best book on software ever written and if you haven't read Code Complete, you're not doing your job! The focus is all about the part of development where you're sticking all the bugs in the code, the construction phase.

When I see that sea of hands go up from the people who haven't read it, I tell everyone that at 9:00 AM the morning after the class they have an assignment: "I don't care what your boss has scheduled for you, I have a much higher priority task: read Code Complete. To ensure you read it, you all owe me a book report. You know, like those ones you did in high school. Don't try to skip out on doing the book report because I have your email address and will be checking up on you!" I try to say that as serious as I can. However, I can't help but laugh and tell the class that if someone actually sent me a book report I would probably have a heart attack in amazement.

Guess what? I had a heart attack today! John Gorski, an SDET (Software Design Engineer in Test) on the Microsoft System Center Configuration Manager team, took one of the internal Microsoft versions of my class and sent me a book report! John was kind enough to allow me post it here. John obviously earned an A+:

Code Complete, 2nd Edition by Steve McConnell is my fourth favorite book of all time. Considering the books that beat it out were The Once and Future King, Surely You're Joking, Mr. Feynman! and The Complete Calvin and Hobbes, this is quite the honor for a software engineering book.

 Code Complete is first a reminder of everything you may have forgotten as a programmer, especially when first coming upon the field of software testing. In first studying software testing, the inherent complexity of software and the sheer number of possible test cases is overwhelming. The idea that a human being could possibly create such a complicated system that works becomes a laughable premise. Code Complete not only reminds the reader that this is possible, but reminds him of exactly how to go about doing it.

 Code Complete presented to me, for the first time, the idea that high-quality software is cheaper to develop, debug and maintain than low-quality software. Imagine that--a product that is cheaper to produce when it's of higher quality! The only other place in life where this seems to be true is the case of in-season produce. The book's frequent allusions to the Pareto Principle bolster the idea that a good programmer is, quite literally, worth his weight in gold (despite the quantity of good programmers that are hardly of a slender build).

 Throughout the book, McConnell is relentlessly practical, even down to seemingly aesthetics-only issues like how to format code or the question of when to comment. Not one of the 850+ pages is without some insight on how to improve your own coding practices. Chances are, even if you're already following the guidelines themselves, McConnell makes the case for these practices with more clarity and more measured reason than all but the most both battle-hardened and well-read coders in his audience.

 Code Complete embraces and eloquently evangelizes the sentiment that programming  is essentially a human task, the most powerful tool for which is the mind. Once we internalize this insight, nothing strikes us as more natural than that a good programmer must look into at least a little psychology and set high standards that must be enforced by his self-discipline alone. Since the primary activity of programming is transferring knowledge amongst human beings and from person to machine, razor-sharp communication skills (non-verbal as well as verbal) are just as critical.

 It's baffling that Code Complete isn't required reading for every aspiring computer scientist and software engineer in the country. No other book is so beneficial to the student's technical future while being so digestible in the course of a lazy summer that may be otherwise full to the brim with the debauchery of youth (tempered by the student's discovery of the tangible value of self-discipline and integrity).

For those of you perusing this blog entry who haven't read Code Complete, you too owe me a book report. I'm not kidding! Post your book review on your blog or in the comments. I also have a challenge for any managers reading this: how about we require reading Code Complete as part of the yearly goals for everyone who hasn't read it?

More Posts Next page »