Setting a Silverlight 2 Startup Breakpoint using WinDBG
As I've mentioned on a previous occasion, hardcore debugging is done with WinDBG and SOS. Visual Studio's debugger just isn't enough for the super interesting scenarios. I've also mentioned that I have no intention of giving a step-by-step introduction into how to use SOS. However, given that debugging is directly related to internals and that most people with advanced Silverlight issues will search for "advanced Silverlight debugging" or the like, I'll provide a little information here and there to help people find what they are looking for quicker.
Here I would like to explain how you can set a break point very early in the Silverlight load process, before your application even loads. This technique will give you a glimpse into the internals of Silverlight to see how things work. It will also help you to step through your method calls from the very beginning in WinDBG.
First, however, understand that Silverlight has three major components that you need to be aware of:
- NpCtrl.dll
- AgCore.dll
- CoreCLR.dll
Each of these components is critical to loading and running a Silverlight application. NpCtrl.dll is a browser plugin which follows the Netscape Plugin API (NPAPI). When your browser sees that you are trying to load Silverlight 2, it will call the NP_Initialize export (a public DLL function). Later, this plug-in will load the AgCore.dll and create a instance of the COM class XcpControl2 to allow NpCtrl to interact with Silverlight (you too can use XcpControl in your COM development). AgCore is the unmanaged part of Silverlight 2. It handles everything from DOM interaction, to rendering, to media play.
At this point you will see the spinning XAP-loading progress bar. When this is complete, NpCtrl will finally load CoreCLR.dll. Upon load, the DllMain function for this DLL is called. This function will start up the CoreCLR, the CLR that Silverlight 2 uses. Upon completion, NpCtrl then calls the GetCLRRuntimeHost DLL export to get the loaded instance of the CLR. The rest of your application is ready to run (as a side note, the order of a few of these things changes under certain circumstances, so you may actually see CoreCLR load AgCore.)
With this information you're ready to start a fresh instance of a web browser (it's crucial that Silverlight 2 isn't already loaded) and attach WinDBG to your browser's process. Upon attach you are given control to the command interface. To set a breakpoint when Silverlight 2 starts, you set a breakpoint when a specific DLL is loaded. Depending on your needs this may be the npctrl.dll, agcore.dll, or coreclr.dll.
You set a breakpoint for a DLL load in WinDBG by using the following command:
sxe ld:DLLNAME
Thus, if you wanted to break at a specific point in the Silverlight loading procedure, you use one of the following commands:
sxe ld:npctrl
sxe ld:agcore
sxe ld:coreclr
If you want to remove these breakpoints, just type the the following:
sxr
Once you have one or more DLL load breakpoints these and resume your application (the 'g' command or F5 in WinDBG) you can test it our by going to http://themelia.netfxharmonics.com/. If successful, WinDBG will break when your DLL loads. In the call stack window (or when you type the 'k' command), you will see a call stack that looks something like the following:
kernel32!LoadLibraryW+0x11
npctrl!GetCLRRuntimeHost+0x112
npctrl!CWindowsServices::CreateCLRRuntimeHost+0x19
agcore!CCLRHost::Initialize+0x12a
agcore!CRuntimeHost::Initialize+0x60
agcore!CCoreServices::CLR_Startup+0x95
agcore!CDeployment::Start+0x27
agcore!CCoreServices::StartDeploymentTree+0x53
npctrl!CXcpBrowserHost::put_Source+0x10f
npctrl!CommonBrowserHost::OnGotSourceDownloadResponse+0x4b
npctrl!CXcpDispatcher::OnReentrancyProtectedWindowMessage+0x284
npctrl!CXcpDispatcher::WindowProc+0x51
USER32!InternalCallWinProc+0x28
USER32!UserCallWinProcCheckWow+0x150
Chances are, though, that you won't see this information like this at first. Instead, you will probably see a series of memory addresses. To view the actual function names, you must configure your symbol paths in WinDBG. These symbols, often called PDBs, will relate the machine information with information from the original source. In our case, we need them to view the function names.
To setup symbols, use the following command, replacing C:\_SYMBOL with a symbol location of your choice (symbols are very important and, therefore, should not be thrown into a "temp" folder):
. symfix SRV*C:\_SYMBOL*http://msdl.microsoft.com/download/symbols;SRV*C:\_SYMBOL*http://symbols.mozilla.org/firefox
You may also enter this information in the window that is shown when you hit Ctrl-S. Either way, this will configure the symbols for both Microsoft's symbol server and Mozilla's. The latter will help you view exactly how far the loading processing has gone while in Firefox.
With the symbols configured, you will then be able see something like the above call stack. If you set the symbols after you had already loaded Silverlight 2 in your browser, then you need to exit the browser, start a new instance, and reattach WinDBG. Regardless, every time a DLL is loaded, WinDBG will automatically access the appropriate Microsoft and Mozilla and download the symbol file for that DLL. This will take time for every DLL that is to be loaded. Expect the WinDBG status bar to read *BUSY* for a long time. It will only load the symbol for each DLL once.
The method I've just described works perfectly for breaking in WinDBG Firefox and IE. But, what about Chrome? Depending on settings, Google Chrome uses either a process-per-tab model or a process-per-site model. While this flexibility is great for daily use, for debugging we can't have processes bouncing around like jack rabbits on cocaine. You need a process that you can attach to that won't run away as soon as you use it. So, to successfully attach WinDBG to Chrome, you should start Chrome up in single process mode. You do this as follows:
chrome.exe --single-process
At this point, you can attach to Firefox, IE, and Chrome for Silverlight 2 debugging in Silverlight.