Some days I wonder how customers debug .NET code.
I've been debugging CLR for many years using a mix of techniques (VS.NET debugger and Windbg and logging of course). To be honest I hate debugging in VS.NET, the debugger just annoys me to no end except for very simple debugging scenarios. Once you go Windbg it is hard to go back.
But Windbg and CLR debugging are not a match made in heaven. They work together like Frankenstein's monster and the angry mob work together.
Today I am debugging System.Data.SqlClient and transaction behavior inside CLR stored procedures and TDS protocol.
I know that transaction state flows over TDS protocol using a combination of TDS ENVCHANGE tokens (server->client) plus TDS mars header (client -> server).
What I see is proper setup of transaction state here:
- Tds: Response from server, Version=7.2 + TDSHeaderFirst: 0x1 - TDSServerResponseData: TokenType: ENVCHANGE - TokenData: Length: 11 (0xB) EnvChangeType: Begin Transaction NewValueLength: 8 (0x8) NewValue: 223338299393 (0x3400000001) OldValueLength: 0 (0x0) TokenType: DONE + TokenData:
0x3400000001 is my transaction id (descriptor). I see it going out on the next request:
- Tds: Query, Version=7.2 + TDSHeaderFirst: 0x1 - SqlBatchData: - AllHeadersData: TotalLength: 22 (0x16) HeaderLength: 18 (0x12) HeaderType: MARS Header TransactionDescriptor: 223338299393 (0x3400000001) OutstandingRequestCount: 1 (0x1) SQLText: exec ClrProc2 N'123'
Next my CLR sproc inserts data into a table which in turn fires a trigger which in turn rolls back the transaction ->
create trigger trig1 on t1 for insert, update as begin insert t2 (f1) values (1) select * from t2 rollback tran end
It is not recommended to do this in a trigger of course! This is only my little test to see TDS protocol behavior. The CLR sproc is supposed to surpress TDS tokens for internally run commands, which is a nice feature. But how does it do this and keep transaction state consistent with client? I expected it to send back a rollback transaction state ENVCHANGE. I don't see it over the wire.
I do see an ERROR token of state 16 coming back and I suspect this is telling SqlClient to reset transaction state.
Ok, back to debugging CLR. I need to know when this TDS response comes back, how it resets the transaction internal state of SqlClient. I know little to nothing about the internals of how it works, but need to figure it out and debug it quickly without wasting time. Via some digging I found this _currentTransaction was getting set and reset when the ENVCHANGEs come in.
Now, how do I set a breakpoint on the change of _currentTransaction in VS.NET debugger?
Let the fun begin.
First, note that I have a debug version of SqlClient that I just freshly built out of my enlistment and shoved into the GAC. This is one important way to please the temperamental gremlin living inside VS.NET debugger. Ok, crank up debugger.
Ok, get to my code, try to step into SqlCommand.ExecuteNonQuery. Debugger does not step into it.
Tools | Options... Debugging | General-> "Enable Just My Code" checkbox is clear. Why does it not step in?
Ok, the gremlin does not like my System.Data.dll. VS.NET cannot find the symbols for a dll I just build on my local machine. The trick to fix this is to place the symbols right next to the dll so that the debugger does not have to look too hard for symbols. Or you can modify symbol path in VS.NET. I choose to place symbols next to dll, the reload symbols from module window.
Cool, got it working. Now how do I set a bp on change of _currentTransaction? This seems to be the tricky part in CLR. In Windbg I would locate address of _currentTransaction and set a ba w4 <address> on it and call it a day. In CLR there is no equivalent as far as I can tell. Yes, I suppose I can manually set breakpoints on every method of the class that _currentTransaction uses in hopes that the methods are used but this is difficult to do in CLR as well (in Windbg I can set breakpoint on all class methods using a wildcard in one command). It is time to go beg the VS.NET debugger gurus to see if they can help me.
Not yet. Let me try this VS.NET 2008 immediate window, I have been meaning to give it a try.
!help '!help': not available while Managed only debugging.
Arg. I found a few basic commands that work, but most Windbg commands don't work:
?_currentTransaction null ?poi(_currentTransaction) The name 'poi' does not exist in the current context
Ok, I give up for now. I talked to CLR folks and they indicated VS.NET does not have data breakpoints for managed code yet. Bummer.