Jump to navigation

Poupou's Corner of the Web

Looking for perfect security? Try a wireless brick.
Otherwise you may find some unperfect stuff here...

Weblog

Now that's debugging in '04!

That short MSDN article, Create a Debugger Visualizer Using Visual Studio 2005 Beta 1, really got my attention.

Like many I have done countless hours of debugging and, most of the time, I'm as much angry at myself as I'm angry with the debugger when I found the flaw. Why ? Because, let's face it, debuggers still do a very bad job at interpreting complex data. The bug is rarely the data itself but without understanding the data you're simply working in the dark.

Personally I often deal with this complex data. Many security structures, like X.509 certificatres, are encoded in ASN.1 (and may be part of a SSL communication session). I also have a long history of binary protocols, like smartcards (ISO 7816 APDU's), lots of communication with strange hardware, etc. The best developers I have seen working (well debugging) such data where the one that could decode, at full or in parts, the data in their head. To be fair it's not that bad, considering they got their job because of their head anyway (or more precisely because of what they had in it) but it's still a big waste of brain power.

Ok so what's so good about this article ? IDE plugins ain't new ?

Yeah, I know, plugins aren't new. But somehow implementing half a dozen COM interface to get a string didn't look a big timesaver either. And generally debugging debugger plugins isn't considered pure joy either (but YMMV).

What was missing was an easy way to get data out of a debugging session. By easy I mean right here, right now, that @$#^ ASN.1 blob looks wrong. It's too late to modify the code, it's too big to be decoded by hand (or it's also to late to do so ;-).

So something easy would be writing an assembly consisting of a single class with a single method to interpret the data, compiling it and, without even restarting the IDE, using the new code as an help to debug the current problem.

We're not there yet but we're getting close!

Actually with Visual Studio .NET 2005 the step are:

  1. Create a new assembly, referencing the debugging assembly;
  2. Implement this single method, Show, in a class implementing IDebugVisualizer;
  3. Add an assembly-level attribute, DebuggerVisualizer, to map the debugging extension with a type;
  4. Compile the assembly [*];
  5. Copy the assembly to a pre-determined location (which can be automated with the compile step);
  6. and use it without restarting Visual Studio!

[*] However compiling this new assembly does require to stop your current debugging session (so hopefully the bug is easily replicable).

There are a few more limitations: debugged types must be [Serializable] (sadly XmlNode isn't) and it doesn't seems to work on array (e.g. byte[]). Those aren't big problems because most types can easily be transformed into strings in the watch list using one of their property, like XmlNode.OuterXml, or another method, like BitConveter.ToString (array).

Here my first try to save strings into files (with different encodings):

using System; using System.Diagnostics; using System.IO; using System.Text; using System.Windows.Forms; [assembly: DebuggerVisualizer (typeof (VisualizerObjectSource), typeof (Poupou.DebugViz.Common.SaveTextToFile), VisualizerUIType.Modal, Target = typeof (string), Description = "Save as text...")] namespace Poupou.DebugViz.Common { public class SaveTextToFile : IDebugVisualizer { private const string filter = "ASCII Text file (*.txt)|*.txt|" + "UTF-8 Text file (*.txt)|*.txt|" + "Unicode Text file (*.txt)|*.txt|" + "UTF-7 Text file (*.txt)|*.txt|" + "UTF-32 Text file (*.txt)|*.txt|" + "Big Endian Unicode Text file (*.txt)|*.txt|" + "All files (*.*)|*.*"; private string GetStringFromObject (object obj) { if (obj == null) return null; // extend as needed - class must be [Serializable] // to work with VisualizerObjectSource if (obj is string) return (obj as string); return null; } private byte[] Encode (string s, int filterIndex) { switch (filterIndex) { case 1: return Encoding.ASCII.GetBytes (s); case 2: return Encoding.UTF8.GetBytes (s); case 3: return Encoding.Unicode.GetBytes (s); case 4: return Encoding.UTF7.GetBytes (s); case 5: return Encoding.UTF32.GetBytes (s); case 6: return Encoding.BigEndianUnicode.GetBytes (s); default: return Encoding.Default.GetBytes (s); } } void IDebugVisualizer.Show (IServiceProvider windowService, IVisualizerObjectProvider objectProvider, VisualizerUIType uiType) { if (objectProvider == null) return; string str = GetStringFromObject (objectProvider.GetObject ()); if (str == null) return; SaveFileDialog dlg = new SaveFileDialog (); dlg.Filter = filter; if (dlg.ShowDialog () == DialogResult.OK) { using (Stream s = dlg.OpenFile ()) { if (s != null) { byte[] array = Encode (str, dlg.FilterIndex); s.Write (array, 0, array.Length); s.Close (); } } } } } }

It's easy to see the possibilities - I, for one, would like to deal with the three X509Certificate classes :-). However the biggest advantage is that because it's simple there no need to do them before you need them. Most of the plugins will probably save you time the first time you use them - at least if you use them wisely ;-).

I sure hope to see something similar, from an simplicity point of view, in other IDEs soon :-)


9/19/2004 23:04:11 | Comments

The views expressed on this website/weblog are mine alone and do not necessarily reflect the views of my employer.