Most of you at the DDC will notice that I'm not at the DDC.  I'm guessing that makes me one of the "juniors" that Bob was referring to. ;)

4.)  How do you build debugging techniques into your driver?  Ie, DbgBreakPoint, ASSERT, etc - which is best in various situations? 

This is one of those personal preference type questions.  For the sake of lineal chatter, I'll just use ASSERT in my commentary.  But when I say "ASSERT" you can make the determination on which one to use given your own working methods.

So that preamble aside, it's on with the show.  I like to have staged text output via a debug trace level as a first tier technique.  Something along these lines;

#define FILTERNAME        TEXT("mydriver")
#define DRIVERNAME        FILTERNAME TEXT(".sys")
#define DRIVERNAME_DBGOUT FILTERNAME TEXT(": ")

#if DBG
#define DBG_TRACE_VERBOSE      0
#define DBG_TRACE_MINIMAL      1
#define DBG_WARNING            2
#define DBG_ERROR              3
#define DBG_FATAL              4 

extern unsigned char DebugLevel;

#define DBG_PRINT( l, _x_ )                   \
    if( (l) >= DebugLevel )      \
    {                                         \
      KdPrint((DRIVERNAME_DBGOUT));         \
      KdPrint( _x_ );                       \
    }
#else  // DBG

#define DBG_PRINT( l,_x_ )

That allows me to compartmentalize failures and thusly allows me to build handlers for each.  The question of what to do in each of those situations is another personal preference. As you can see from the error levels above, I follow patterns of building in handlers that fall in to - terminal, really annoying, nuisance, does it work and assuage.

I'll ASSERT more frequently for the nuisance issues, but also for the terminal, and some of the really annoying issues.  Nuisance issues such as that HANDLE disappeared while I was using it, better ASSERT. This is usually when I find that the other thread using that handle wasn't synch'd properly...laugh all you want, you've done it too. :)

Using those levels as a guide, I craft an appropriate follow up action.  But here I let the driver tell me what to do and it does that based on its end usage.  For example;

  Status = IoCreateDevice (
      DriverObject, 
      sizeof (DEVICE_EXTENSION), 
      &DriverName,
      FILE_DEVICE_UNKNOWN, 
      DeviceType, 
      FALSE, 
      &DeviceObject);
  if (!NT_SUCCESS (Status)) 
  {
    DBG_PRINT (DBG_FATAL, TEXT(("Failed IoCreateDevice: 0x%x\n", Status)));
    ASSERT (DeviceObject);
    return Status;
  }

Would I use that ASSERT in a test driver?  Not likely, all too often our test cases attempt to invoke failures in just those sort of areas and we run chk (debug, no_opt, etc.) builds in our labs frequently. So that ASSERT would be triggered constantly.  You could also argue that in a shipping driver you would hit it in a lab under low resource simulation testing, but unless you plan on running low resource simulation 24/7, the nuisance factor is diminished greatly.  But having the debug print there is a means for you to see if something happened in a retail / fre build of the driver.  You can also wrap Windows logging methods with the same mentality such as WPP and ETW.

All of the above starts with the driver though.  The more complex the driver, the more robust the debugging logic, so some of my internal test drivers have one or two ASSERT calls and maybe a couple of lines of debug print statements.  So if you peek at the 1394vdev hybrid sample you can get a better idea of my patterning.  The lack of ASSERT calls was a design choice based on the pattern established by the previous versions of that sample driver.

So for those of you at the DDC, enjoy the DDC!  If you feel like making Peter laugh, ask him why I'm such a dork.

*Currently playing - Gravity Eyelids Porcupine Tree