Saturday, September 10, 2011

Inject Code into Silverlight Application

Snoop is fantastic tool for WPF developers that makes bugs elimination an easy game. Silverlight developers on the other hand never had good open source alternative. Most of the Silverlight dissecting tools that I've seen so far (one, two) inject themselves into Silverlight applications by intercepting .xap files and modifying assemblies content. While this approach is viable and reliable it still has certain drawbacks:

#1 Cannot attach to already running Silverlight application without reloading page.
#2 Some .xap interceptors work only with IE browser or require complex scaffolding.

I'm going to show you how to inject a Silverlight assembly into running Silverlight process. No page reloads, no .xap hijacks, no browser ties and no unnecessary CO2 emission. Well, not sure about the latter. But before we start let me introduce a special open source guest of this post...

slinject

This command-line utility is a baby of engineering curiosity. It can inject assembly into a Silverlight process of your choice:


You can grab the binaries here and the source code (C#) at github. Before you can start using it make sure to run
slinject --install
It's a one time operation which allows assembly injection at runtime and requires administrator privileges. Afterwards application can be used without admin rights.

What the heck?! Or how it works

As it often happens the idea is very simple: Attach debugger to Silverlight application and evaluate "Assembly.Load()" expression. You could always do it with Visual Studio and certain debugging dexterity. But can we do it without Visual Studio? - Sure. Can we opt out debugging dexterity? - Doh.

Silverlight comes with a bunch of interesting yet vaguely documented libraries. One of them is dbgshim.dll. This library is debugger's door into the Silverlight world. It's used by Visual Studio debugger, so why can't we use it too? Really, why? Lack of good documentation sounds like a lame excuse and a challenge for engineering mind, right?

After wondering several nights in Morpheus' kingdom ICorDebug namespace I managed to write a console based debugger for Silverlight. It could set a breakpoint at any method and even sometimes perform expression evaluation (aka Function Evaluation or FuncEval).

The Problem

We need to find a method where we can set a breakpoint and be sure it will be invoked in very all Silverlight application. The plugin has no standard windows message queue. But there is a close analog: JoltHelper.FireEvent() in System.Windows.dll. This guy is called by CoreCLR often enough. Even if your application is a blank window, move around mouse cursor and you'll get FireEvent() called.

In the sentence above ("console debugger could perform expression evaluation"), did you notice the "sometimes" word? The truth is you cannot evaluate expressions when debugger stops in optimized code. And JoltHelper is optimized indeed...


A Solution

Apparently Silverlight's jitter respects .ini-based configuration and you can request it to not optimize the resulting machine code. The biggest trick here is to save .ini file in UTF-16, Little endian encoding. Anything else is rejected.

To improve performance Silverlight creates native images for standard assemblies. It's pretty much the same as NGen, but the tool is called Coregen and images are stored in the Silverlight installation folder. Coregen respects .ini file as well and generates debugger-friendly native images when requested.

When you execute
slinject --install
slinject creates System.Windows.ini file with corresponding jitter settings, removes old native image for System.Windows.dll and asks Coregen to create debuggable native image. To revert these changes simply call:
slinject --uninstall


Conclusion

So is slinject a safe way to inject assemblies? - Absolutely no! Killing a thread looks even safer than interrupting it at some point and causing it to run arbitrary code (which is how expression evaluation works). Mike Stall describes why func eval is evil but a useful one. That said please be ready to browsers stop working, slinject crashing and financial crisis looming.

To make it even worse, I'm not an expert neither in COM nor ICorDebug. Everything described in this post I learned through experimenting, and most likely I made a lot of mistakes in the code. But hey, it's an open source project - feel free to contribute and fix it.

If you found this post useful - please leave a comment. If you didn't find it useful... leave a comment as well - I would love to hear your feedback :) (and I mean it!).

3 comments:

  1. Thanks for your post!

    I started adapting the MdbgEngine sample for Silverlight about a year ago and wrote a very handsome exception-catcher called RedFlag, which I intended to use as a support tool for my company, Red Gate.

    ftp://support.red-gate.com/utilities/redflagdebugger.zip

    It was hard-going. All the MS blogs say nothing but "it's exactly the same as managed debugging" but in truth finding the location of dbgshim and attaching properly are undocumented.

    I will see if I can integrate some of your stuff because as we know you don't get proper variable names and miss out on other information like function arguments when optimizations are on.

    With regular .NET, I turn the optimizations off at assembly load... but you have to launch the app to do that and Silverlight only allows "attach".

    Thanks!

    Brian Donahue
    http://www.simple-talk.com/community/blogs/brian_donahue/default.aspx

    ReplyDelete
  2. Thank you for sharing, Brian! Nice idea with exceptions trap.

    I can't agree with you more about lack of documentation. Huge pain in the ... but still very pleasurable stuff when it works :).

    By the way, here is one more hidden feature of Silverlight runtime. When you attach with debugger to running Silverlight app - modules are optimized, but when you refresh page without detaching - they are loaded not optimized. Hope this helps your further work on debugger.

    Cheers :).

    ReplyDelete