Debugger web service magic
I am going to talk today about the 'magic' that the debugger does to improve Web service debugging. The debugger has two features in this area:
- The debugger can automatically step from a web service client, into the web server, without requiring the user to manually attach a debugger to the web service
- When stopped inside a web method, the debugger can show a logical callstack. This has the server thread on top, and the client thread on the bottom.
I think these features have a certain amount of 'wow!'. Its pretty cool to give a demo of stepping into the web service without caring what computer/process the web service is implemented in.
Implementation of stepping:
This feature works because the debugger has hooks into System.Web.Services.dll. The debugger gets four events for every web method call:
- The call is leaving the client app.
- The call has entered the server
- The call is returning from the server
- The call has returned to the client
When a call leaves the client app, System.Web.Services checks if the application is running under a managed debugger. If so, it will try and load a component provided by VS (csm.dll) into the client application. It will tell csm.dll that a call is leaving the client app, and will also give the target machine. Csm.dll will then notify the attached debugger so that the attached debugger can try and prepare the destination machine. Lastly, csm.dll creates a memory blob and returns it to System.Web.Services. System.Web.Services will base64 encode this memory blob, and attach it as a custom header to the web method call. The memory is opaque to System.Web.Services, but it needs to contain a GUID to identify a logical thread that the call is apart of, a DWORD to describe which call within the logical thread we are at, and an identifier so that the remote machine can determine what debugger is attached to the client process.
When the call arrives at the server, ASP.NET will base64 decode the custom header. If the web service has debugging enabled ('debug=true' in web.config), it will then load up csm.dll into the ASP.NET process. This component is able to understand the opaque buffer. It will read the identifier of which debugger is attached to the client application, and contact that debugger.
When the debugger finds out that the call entered the server, it will then attach to the web server process. The debugger was also given the name of the function which is about to be executed. The debugger sets an invisible breakpoint on that function, and then continues execution of the web server. The web method will then be called, and the step will complete.
The logical callstack displays the server's thread and the client's thread together in one big 'logical' callstack. Something like this:
To enable it, right click on the callstack window, and select 'Include calls to/from other threads'.
Implementation of the logical callstack
When a call first leaves the client, it creates a GUID to represent the logical thread. When the call lands in the server, the server will remember this GUID. If the server happens to make a call into another server, this same GUID will be passed along. The debugger also remembers these GUIDs. It keeps state about where each logical thread is.
Limitations of this magic:
- You can only step into synchronous web method calls. The limitation here is really a UI one. We don't have some special step command to step into asynchronous calls. Maybe someday.
- Slight performance hit. You probably won't notice unless this happens to disturb a race condition that you are trying to debug.
- Don't try this in VS 7.0. In the last days of 7.0, ASP.NET improved their security by changing the way they launched aspnet_wp.exe so that it defaulted to an unprivileged 'ASPNET' account instead of LocalSystem. I think they did the right thing, but it completely broke this feature. Try 7.1 instead.
- Logical callstack doesn't work unless you run under the debugger. You can't attach to the web server after the call has started, and expect to get a logical callstack.
- For STA applications (applications with the STA attribute on the main function):
- Can only step into web services from the main thread (fixed in Whidbey). Fix is to remove the STA attribute from the main thread.
- Application will hang if main thread is blocked when a web method call starts/stops (fixed in Whidbey). Fix is to pump messages on the main thread, or to remove the STA attribute.
- Some 3rd party web servers are unhappy with the causality header. This is because the causality header was fairly large in 7.0/7.1, this is fixed in Whidbey.