So version 7.6 of .NET Reflector added Visual Studio 2012 integration. I’ve already been really excited by the new IDE as it dramatically reduced memory usage and just seems snappier over all. Additionally, it doesn’t upgrade csproj files so I can use it without breaking those still on VS2010. Now with the ability to step into third party source code, I’m getting a little too excited about all of this. I used Reflector to take apart PowerGUI but with 100% static code analysis. I wanted to write a post about how awesome debugging a third party assembly can be. So I figured…
It’s amazing how different code can look when you see it executing rather than when you are looking at it statically. I decided to play with the PowerShell Profiler project I made and step in from there. With some help from the RedGate guys I got System.Management.Automation to load un-optimized. Here is what I had to do:
- Environment variable COMPLUS_ZAPDISABLE=1
- .NET Framework Debugging Control Ini file for the top-level “Visualizer” process
- Just My Code
- Require source files to exactly match the original version
The final step is to decompile the System.Management.Automation assembly. You can do this by clicking the .NET Reflector menu item and selecting Choose Assemblies to Debug. It took about 5-7 minutes to generate the debug information. Reflector automatically throws the “Debug Cache” folder into the list of symbol folders in the debugger settings. You’ll know it’s working when you look in the Modules window see that the System.Management.Automation assembly is not optimized.
Be careful. The next section is dangerous.
So starting at the top of the call stack I worked my way down from the ScriptBlock into an internal “Interpreter” class. These are some of the interesting things I’ve run into. The ScriptBlock itself is abstract.
The real implementation I’ve seen used is the CompiledScriptBlock class. As it’s aptly named it is probably compiled. I set a break point right inside the Invoke method that is used to run a script block. If you notice we can access all the debugging information that we could with our own code. I have a simple “Get-Process” running in this example.
Once we have grabbed the current runspace we actually do the invoke. This involves creating a new Pipe and then invoking it.
Inside the interpreter we take our current frame and iterate over the instructions in that frame. The instructions are then run. Here we can see what happens in a LoadFieldInstruction.
I actually see the interpreter called twice with two different frames. The second time through I noticed some more use of dynamics. After changing the script to a “Start-Process Notepad” it was obvious the second run through was the actual execution of the script block. The first execution may be initialization of some kind.
It is simply amazing how well I can step through the decompiled source code once everything is setup correctly. It feels like I’m debugging my own code. Oisin might be right…
What does this tell us about PowerShell execution? I am not smart enough to talk to that. What does it tell us about the PowerShell team? They are extremely smart. What does this tell me about myself? That I should probably find a new hobby.