Writing a Managed Internet Explorer Extension: Part 4–Debugging
Picking up where we left of with Writing a Managed Internet Explorer Extension, debugging is where I wanted to go next. I promise I’ll get to more “feature” level stuff, but when stuff goes wrong, and it will, you need to know how to use your toolset. .NET Developers typically write some code and press F5 to see it work. When an exception, the debugger, already attached, steps up to the plate and tells you everything that is wrong. When you write an Internet Explorer Extension it isn’t as simple as that. You need to attach the debugger to an existing process, and even then it won’t treat you like you’re use to. Notably, breakpoints aren’t going to launch the debugger until the debugger is already attached. So we have a few options, and some tricks up our sleeves, to get the debugger to aide us.
The simplest way to emulate a breakpoint is to put the following code in there:
Think of that as a breakpoint that is baked into your code. One thing to note if you’ve never used it before is that the Break method has a
[Conditional(“DEBUG”)] attribute on it – so it’ll only work if you are compiling in Debug. When this code gets hit, a fault will occur. It will ask you if you want to close, or attach a debugger. Now is your opportunity to say “I want a debugger!” and attach.
It’ll look like just a normal Internet Explorer crash, but if you probe at the details, “Problem Signature 09” will tell you if it’s a break. When working on a BHO, check this every time IE “crashes” – it’s very easy to forget that these are in there. It’s also important that you compile in Release mode when releasing to ensure none of these sneak out into the wild. The user isn’t going to look at the details and say, “Oh it’s just a breakpoint. I’ll attach and hit ‘continue’ and everything will be OK”. Once that’s done, choose Visual Studio as your debugger of choice (more on that later) and you should feel close to home.
This is by far one of the easiest ways to attach a debugger, the problem with it is it requires a code change to get working, meaning you need to change the code, close all instances of IE, drop in the new DLL, restart Internet Explorer, and get it back into the state it was. A suggestion would be to attach on
SetSite when the site isn't null. (That’s when the BHO is starting up. Refresher here.) That way, your debugger is always attached throughout the lifetime of the BHO. The disadvantage of that is it’s get intrusive if you like IE as just a browser. You can always Disable the extension or run IE in Safe Mode when you want to use it as an actual browser. If you take this approach, I recommend using
Debugger.Launch(). I'll leave you to the MSDN Documents to understand the details, but Launch won’t fault the application, it will skip straight to the “Which debugger do you want to use?” dialog.
Attaching to an Existing Process
You can just as well attach to an existing process like you normally would, but there is one drawback: “Which process do I want to attach to?” In IE 8 that is a question that can be difficult to answer. Each tab has it’s own process (a trend in new generation browsers – IE was the first to support it). You will have at minimum of two IE processes. One for each tab, and one per actual instance of IE acting as a conductor for the other processes. Already, with just a single tab open, you have a 50/50 chance of getting it right if you guess. Visual Studio can give us some help though. If you pull up the Attach to Process Dialog, you should see your two instances of IE. The “Type” column should give it away. We want the one with Managed code in it (after all, the title of this blog series is "Writing a Managed Extension”).
Once you’re attached, you can set regular breakpoints the normal way and they’ll get hit. Simple!
It isn’t quite as easy when you have multiple tabs open – sometimes that’s required when debugging a tricky issue. You have a few options here:
- When building a UI for your BHO (It’s a catch 22 – I know I haven’t gotten there yet) have it display the PID of the current process. That’s easy enough to do using the Process class. You can dumb it down a little more though and write a log file in a safe location (IE is picky where BHOs write to the File System Refresher here).
- Attach to all tab processes. That can lead to a lot of confusion of which tab you are currently in, because if you have two tabs open – and a breakpoint gets hit – which tab did it? The Threads Window should help you there if that is the route you choose.
- Always debug with a single tab, if you can.
There is a trick you can do in Visual Studio to gain access to some additional debugging features. Hopefully this isn’t brand new material to everyone, but for some I would suspect it is. If you manually choose what you want to attach, include managed code and Native. Attaching to Native is very helpful if you are trying to debug a COM Marshaling issue, and plenty of other issues. We can start a the Managed Debugger Extension to diagnose issues at the CLR level, and even poke at the CLR’s native memory and objects. Once attached in Visual Studio with Native code, get to a breakpoint or pause, and launch the Immediate Window. Type
.load sos and hit enter. If it worked, you should get a message like extension “C:\Windows\Microsoft.NET\Framework\v4.0.30319\sos.dll loaded”. There are many blogs out there about SOS (Son of Strike). I may blog on it later. Some useful commands are:
Pretty self explanatory. Shows you some available commands.
- !dso (!DumpStackObjects)
Does a dump of all CLR objects that are in the stack.
Dumps the entire heap. Careful! - That really means the entire heap. A more generally useful use of dumpheap is to specify the -stat flag (
!dumpheap -stat). This gives you general statistics of the heap. It will tell you which objects are in memory, and how many of them there are. This is useful starting point if you believe there is a memory leak - this can at least tell you what you are leaking.
- !soe (!StopOnException)
Again, I feel that the name of this is pretty self explanatory. The usage of it is a little tricky to beginners. A simple example would be, “I want to stop whenever there is an OutOfMemoryException”. This is useful for some types of exception, OOM is a good example. The problem with debugging an OOM in a purely managed way is the CLR is dying by the time the exception happens, so you will get limited information by slapping a debugger on the managed code. For an OOM, a
!dumpheap -stat is a good place to start. Other examples where this is useful are Access Violations (more common when doing Marshaling or Platform Invoke), Stack Overflows, and Thread Aborts. The usage is
!soe -create System.AccessViolationException.
This will dump the CLR's stack only. The Native stack is left out. This is the same as normal managed stacks that you have looked at. It has some cool parameters though. The -p parameter will tell you what the values of the parameters that were passed into it. Often, it will be the address of what was passed in. Use
!DumpObject and pass in the address to figure out exactly what it was. The -l flag dumps the locals in the frame, and –a dumps both parameters and locals.
This is like CLRStack but on steroids. It has the managed stack like CLRStack, but also has the Native stack. It’s useful if you use Platform Invoke. This command is best used outside of Visual Studio and instead in something like WinDbg – more on that further down.
That’s the tip of the iceberg though. The complete documentation on MSDN is here. That lists commands that !help doesn’t list – so have a look. However, you’re not getting your money’s worth by doing this in Visual Studio. Visual Studio is great for managed debugging and using SOS, but when you want to use the Native commands, such as
!analyze Visual Studio falls short. In addition, SOS is limited to the debugging functionality that Visual Studio provides it – you may often see a message like this: “Error during command: IDebugClient asked for unimplemented interface” Visual Studio doesn’t fully implement these features that SOS is asking for.
Other debuggers, like WinDbg, are significantly more powerful at the cost of they aren’t as simple to use. If there is demand for further details, I’ll post them. Using WinDbg is fairly similar, once you are attached, run
.load C:\Windows\Microsoft.NET\Framework64\v4.0.30319\sos.dll. In WinDbg, you need to specify the full path. In addition, you will want to get symbols from Microsoft’s Symbol Server. There are symbols for mscoree and mscorwks. Having symbols for these can significantly help diagnose native to managed (and vice-versa) transitions.