#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...
slinjectThis 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 --installIt'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 worksAs 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
The ProblemWe 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 SolutionApparently 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 --installslinject 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:
ConclusionSo 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!).