Debugging Silverlight apps using WinDbg
While working on a silverlight application for my current engagement, we were seeing some weird memory issues and application crashes. I won't bore you with the initial investigation of the issue, but it turns out we need to delve deep into what was going on behind the scenes. A fellow colleague and friend, Miguel Madero, was also encountering similar issues. He also did some work in debugging the issues he was seeing, and because they were similar to mine, I had a little headstart in where to look. Note: I know that Miguel will also be publishing some debugging tips around Silverlight and he is a Silverlight gun so keep an eye on his blog for more details.
At any rate, our silverlight apps, after opening certain pages many times (> 8), the application would crash. No real information why, and the Visual Studio debugger wasn't helping a lot as the exception it was faulting on was just the final exception when things got too much.
In Visual Studio, turning on Catch all first chance exceptions wasn't helping either as there were too many other first chance exceptions that were confusing things (and yes, many framework related ones).
So, using WinDbg, we decided to dig deeper. The rest of this post shows how we debugged some silverlight issues using WinDbg using some general examples, and a few specific ones.
Getting Started.
You can create crash dumps to examine using a tool like adplus (part of the debugging tools) which I did, but I also prefer to attach to the running process itself where possible so I shall use that here.
1. Loaded up WinDbg
2. Loaded the SOS module for silverlight which is the managed debugging extension.
3. Set the symbol path to a local directory on my system as well as the microsoft symbol servers when the tool needs to download/resolve symbols.
You can do this via the command line
or via the user interface
4. Fire up your SIlverlight application in a standard Internet Explorer browser window. Do not invoke debugging within Visual Studio (ie. press the 'Play' button or hit 'F5' to start the app) as this will attach its own debugger. Then attach WinDbg to the 'iexplore.exe' process.
5. Now we are ready to begin actual debugging. Typing 'g' into the command line gets the debugee running, and WinDbg monitors the application for first chance exceptions.
One thing that is not so apparent within Silverlight, particularly when using the MVVM (Model-View-ViewModel) pattern which typically uses DataBinding extensively, is when data binding errors occur. The framework tends to hide these little gems away, and sometimes, it just appears like its not working. Note that these will also appear in the Visual Studio debug output window but I find it actually easier to have the WinDbg window running side by side with the browser to watch all exceptions as they occur.
By way of example, lets imagine the view model has this property, SelectedThing.
Now lets also imagine we have a ComboBox databound to this using the following XAML.
<ComboBox x:Name="DropList" Grid.Row="1" Height="30" ItemsSource="{Binding SomeData}" DisplayMemberPath="Name" SelectedIndex="{Binding SelectedThing, Mode=TwoWay}" />
Finally, when we fill up/gather our data, we set the SelectedThing to an initial value:
SelectedThing = SomeData[0];
The astute among you can probably already see where the error is, but lets persist for the sake of example. We load the app and see that the ComboBox is not having its selected item bound to anything.
At this stage, no errors are prevalent, other than we have no SelectedItem in the ComboBox. Hpwever, looking at the WinDbg window, shows us that there were some exceptions occurring.
Specifically, we can see that an exception occurred with a BindingExpression, in which a converter failed to convert a value for 'SomeItem'. The entire message is shown below, and its pretty verbose about what failed.
(Again, this is the same output as the Visual Studio debug output window)
System.Windows.Data Error: 'MS.Internal.Data.DynamicValueConverter' converter failed to convert value 'TestSLCrap.SomeItem' (type 'TestSLCrap.SomeItem'); BindingExpression: Path='SelectedThing' DataItem='TestSLCrap.ViewModel' (HashCode=64479624); target element is 'System.Windows.Controls.ComboBox' (Name='DropList'); target property is 'SelectedIndex' (type 'System.Int32').. System.InvalidOperationException: Can't convert type TestSLCrap.SomeItem to type System.Int32.
at MS.Internal.Data.DefaultValueConverter.Create(Type sourceType, Type targetType, Boolean targetToSource)
at MS.Internal.Data.DynamicValueConverter.EnsureConverter(Type sourceType, Type targetType)
at MS.Internal.Data.DynamicValueConverter.Convert(Object value, Type targetType, Object parameter, CultureInfo culture)
at System.Windows.Data.BindingExpression.ConvertToTarget(Object value).
It tells us the value to convert, the binding path and the property. Pretty easy to fix. In the XAML we simply change this:
SelectedIndex="{Binding SelectedThing, Mode=TwoWay}"
to this:
SelectedItem="{Binding SelectedThing, Mode=TwoWay}"
Again, pretty simple and actually an easy error to do if you are going fast and accepting the Intellisense suggested property without really looking. To bring this back to my problematic silverlight app I mentioned, while debugging I found numerous instances of binding issues where properties were NULL when not expected and a few others. Eliminating these certainly made the app run better but it was still crashing.
So, I gotta dig deeper. I had a look at some of the exceptions on the heap.
0:005> !dumpheap -type Exception
Address MT Size
03a21024 038c72d0 72
03a2106c 038c7398 72
03a210b4 038c7458 72
03a210fc 038c750c 72
03a21144 038c750c 72
03a24edc 060a5668 32
03a25a4c 060d51e4 32
03a25a6c 060d52c0 32
03a2dac4 060dd240 32
03a3d254 0321f48c 12
03a3d26c 0321f510 12
total 11 objects
Statistics:
MT Count TotalSize Class Name
0321f510 1 12 System.Text.DecoderExceptionFallback
0321f48c 1 12 System.Text.EncoderExceptionFallback
060dd240 1 32 System.EventHandler`1[[System.Windows.ApplicationUnhandledExceptionEventArgs, System.Windows]]
060d52c0 1 32 MS.Internal.Error+GetExceptionTextDelegate
060d51e4 1 32 MS.Internal.Error+ClearExceptionDelegate
060a5668 1 32 System.UnhandledExceptionEventHandler
038c7458 1 72 System.ExecutionEngineException
038c7398 1 72 System.StackOverflowException
038c72d0 1 72 System.OutOfMemoryException
038c750c 2 144 System.Threading.ThreadAbortException
Total 11 objects
Hmmm, not too much interesting here as these exceptions. Lets dump the current stack objects to see what that yields:
0:005> !dso OS Thread Id: 0xa54 (5) ESP/REG Object Name 0179E85C 04084278 Views.DataDisplayView 0179E880 03a82684 System.Windows.CoreDependencyProperty 0179E884 04084278 Views.DataDisplayView 0179E89C 04084278 Views.DataDisplayView 0179E8BC 03a82684 System.Windows.CoreDependencyProperty 0179E8C0 04084278 Views.DataDisplayView 0179E8CC 0450f6d8 System.Windows.Controls.RadioButton 0179E8E0 03a82684 System.Windows.CoreDependencyProperty 0179E8E4 04084278 Views.DataDisplayView 0179E8F0 03a82684 System.Windows.CoreDependencyProperty 0179E8F4 04084278 Views.DataDisplayView 0179E904 04084278 Views.DataDisplayView 0179E910 04084278 Views.DataDisplayView 0179E914 04084278 Views.DataDisplayView 0179E918 04084278 Views.DataDisplayView 0179E91C 04088ca0 System.Windows.Controls.RadioButton 0179E938 04088ca0 System.Windows.Controls.RadioButton . . . majority of listing omitted for brevity 0179E980 0450f6d8 System.Windows.Controls.RadioButton 0179E990 0455641c System.Windows.RoutedEventArgs 0179E994 0450f6d8 System.Windows.Controls.RadioButton 0179F7C4 03a6f9d0 Ninject.Core.Injection.DynamicMethodInjector 0179F7C8 0425fcd8 System.Object[] (System.Object[]) 179F9BC 03a92720 PresentationLayer.UI.UserControls.MenuItem 0179F9E4 03a92c00 System.Windows.Input.MouseButtonEventHandler 0179FA04 03a92720 PresentationLayer.UI.UserControls.MenuItem 0179FA08 0425f898 System.Windows.Input.MouseButtonEventArgs 0179FA44 0425f87c System.String M@21 0179FB24 03a2526c MS.Internal.FrameworkCallbacks+FireEventDelegate 0179FBC8 03a2526c MS.Internal.FrameworkCallbacks+FireEventDelegate
Now this is much more interesting, as you can see multiple references to Views.DataDisplayView where only 1, should be present. I poked around the objects themselves and found similar patterns. Using the 'gcroot' command, I could see what objects were 'hanging around'.
0:005> !gcroot 04084278 Note: Roots found on stacks may be false positives. Run "!help gcroot" for more info. Scan Thread 5 OSTHread a54 ESP:179e85c:Root: 04084278(Views.DataDisplayView)-> ESP:179e884:Root: 04084278(Views.DataDisplayView)-> ESP:179e89c:Root: 04084278(Views.DataDisplayView)-> ESP:179e8c0:Root: 04084278(Views.DataDisplayView)-> ESP:179e8e4:Root: 04084278(Views.DataDisplayView)-> ESP:179e8f4:Root: 04084278(Views.DataDisplayView)-> ESP:179e904:Root: 04084278(Views.DataDisplayView)-> ESP:179e910:Root: 04084278(Views.DataDisplayView)-> ESP:179e914:Root: 04084278(Views.DataDisplayView)-> ESP:179e918:Root: 04084278(Views.DataDisplayView)-> ESP:179e91c:Root: 04088ca0(System.Windows.Controls.RadioButton)-> 0408900c(System.Windows.DataContextChangedEventHandler)-> 04088fd0(System.Windows.Data.BindingExpression)-> 04076c74(Views.DataDisplayViewViewModel)-> 04076eb8(System.Collections.ObjectModel.ObservableCollection`1[[MyApp.DataEntities.WeekCommence, MyApp.DataEntities]])-> 0409dbb0(System.Collections.Specialized.NotifyCollectionChangedEventHandler)-> 04085a34(System.Windows.Controls.ComboBox)-> 04085c84(System.Windows.Controls.SelectionChangedEventHandler)-> 04084278(Views.DataDisplayView)-> ESP:179e938:Root: 04088ca0(System.Windows.Controls.RadioButton)-> 0408900c(System.Windows.DataContextChangedEventHandler)-> 04088fd0(System.Windows.Data.BindingExpression)-> 04076c74(Views.DataDisplayViewViewModel)-> 04076eb8(System.Collections.ObjectModel.ObservableCollection`1[[MyApp.DataEntities.WeekCommence, MyApp.DataEntities]])-> 0409dbb0(System.Collections.Specialized.NotifyCollectionChangedEventHandler)-> 04085a34(System.Windows.Controls.ComboBox)-> 04085c84(System.Windows.Controls.SelectionChangedEventHandler)-> 04084278(Views.DataDisplayView)-> Scan Thread 13 OSTHread 16b4 Scan Thread 14 OSTHread 1274 Scan Thread 15 OSTHread 15a8 Scan Thread 16 OSTHread 1330
I could see many instances of references to properties of type ObservableCollection<>. Looking at the pinned/rooted objects for a view other instances of the 'Views.DataDisplayView' objects, revealed similar results, some showing many instances of references to properties that implemented ObservableCollection<>.
Obviously, something was subscribing to the collection changed events of the ObservableCollections, and simply not letting go. Whether this be the DataBinding infrastructure of Silverlight or a result of some of the dodgy code in the application, its currently hard to say as the code is quite convoluted.
I can say this however. Changing the properties listed in the WinDbg sessions from ObservableCollections to simple generic List<> types completely solved my crashing issue. Simply changing these types involved a bit of work to now manually raise the appropriate property changed events when these properties are changed, but at least my application doesn't crash.
As a result, I now keep WinDbg loaded during most of my silverlight development, with the symbol path and symbols loaded and ready to go. its easy to just attach to the iexplore.exe process, make sure its all good, and be on my way. I know that WinDbg can save all this workspace information, but for some reason, sometimes it doesn't so I generally keep it loaded and its small enough anyway.
Hope this proves informative for someone.