Archive for the ‘SOS’ Category
Why isn’t the !bpmd in sos / windbg not working?
I recently noticed another blog post refer to one of my post. The issue was, sos wasn’t enabling the break-points on non-jitted functions. The classic example being “Main”. Thanks to Steve I have been using sosex and not sos for setting break-points.
From my previous post you can understand how CLR is using clrn/CLRNotificationException to notify sos/sosex on JIT. With this information when I looked at the rotor code, I noticed an interesting member variable “g_dacNotificationFlags”. So I decided to check the value of this variable when using !bpmd from sos and !mbm from sosex.
.if (dwo(mscorwks!g_dacNotificationFlags) = 0) {.echo bp not set } .else {.echo bp set}
It was “0″ when using sos and “1″ when using sosex. Now I had to change the value to “1″ and check if the break-point becomes active when using sos’s !bpmd. FYI I don’t have private symbols and haven’t seen CLR Code. Here is the code to set the value to “1″.
ed mscorwks!g_dacNotificationFlags 00000001
And not to my surprise the !bpmd seems to work for non-jitted function with the above hack. FYI we don’t have to resort to this to get !bpmd to work. If the !bpmd is set after load of mscorjit/clrjit it would work as expected.
Using sosex within windbg to understand IL and Assembly code
Sometimes when debugging managed code within the debugger I would like to see the C# code ,the IL translation for the managed code and the Assembly code for the IL. For example I recently learned that callvirt MSIL instruction must do the null-check before invoking method.
C:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication13\Program.cs @ 18:
00bc26d8 8b4dec mov ecx,dword ptr [ebp-14h]
00bc26db 3909 cmp dword ptr [ecx],ecx //NULL Check
00bc26dd ff1508a82900 call dword ptr ds:[29A808h] (System.String.ToLower(), mdToken: 0600031d)
00bc26e3 8945e8 mov dword ptr [ebp-18h],eax
00bc26e6 8b45e8 mov eax,dword ptr [ebp-18h]
00bc26e9 8945ec mov dword ptr [ebp-14h],eax
I am not an assembly code expert. The above output is from “!u” sos command. It doesn’t show the c# code except the line number and it is missing IL translation.
The “!mu” from sosex does what I want. It is not yet documented because it is not yet stable as per the output of the command. Here is the output for the same call-stack using sosex’s !mu.
0:000> !mu
THIS COMMAND IS UNDOCUMENTED AND NOT YET STABLE.
test = test.ToLower();
IL_001a: ldloc.0 (test)
IL_001b: callvirt System.String::ToLower
00bc26d8 8b4dec mov ecx,dword ptr [ebp-14h]
00bc26db 3909 cmp dword ptr [ecx],ecx
00bc26dd ff1508a82900 call dword ptr ds:[29A808h]
00bc26e3 8945e8 mov dword ptr [ebp-18h],eax
IL_0020: stloc.0 (test)
00bc26e6 8b45e8 mov eax,dword ptr [ebp-18h]
00bc26e9 8945ec mov dword ptr [ebp-14h],eax
The above output has c#,IL and assembly.
Script to !SaveAllModules in .NET 4.0 SOS within Windbg
The .NET 4.0 sos doesn’t have save all modules (!SaveAllModules) command. It only has !SaveModule. Recently I was debugging a .NET 4.0 process for which I had to save all the modules. Here is a script that does !SaveAllModules.
!for_each_module .if ($spat ("${@#ImageName}","*.exe")) { !SaveModule ${@#Base} c:\temp\${@#ModuleName}.exe } .else { !SaveModule ${@#Base} c:\temp\${@#ModuleName}.dll }
Dumping ASP.NET Session (x86 /x64) within Windbg
This post is going to be about dumping ASP.NET session objects using Windbg. I had recently answered a stackoverflow question in which someone wanted to dump ASP.NET session objects for 64-bit IIS (x64). I thought why not blog about the same which might be useful to others. The challenge is to write one script that should work in both x86/x64. FYI there is a script from Tess that does dump out the session contents, AFAIK it will not work on x64 and my script iterates through the array using the array length instead of using “.foreach /pS 2 /ps 99” which is somewhat cleaner.
Here is the script for dumping ASP.NET session objects within Windbg / CDB
$$$ Dump the ASP.NET Session objects within windbg/cdb
$$$ Platform : x86 / x64
$$$ Naveen Srinivasan http://naveensrinivasan.com
$$$ Usage: $$>a<"c:\Debuggersx86\dumpsession.txt" 000007fef4115c20
$$$ where 000007fef4115c20 is the MethodTable pointer System.Web.SessionState.HttpSessionState
r @$t9 = @$ptrsize
$$ $t9 register contains pointer size
$$ $t8 register contains the next offset of the variable
$$ $t7 register contains array start address
.if (@$ptrsize = 8 )
{
$$$ x64
r @$t8 = 10
r @$t7 = 20
r @$t6 = 10
}
.else
{
$$$ x86
r @$t8 = 6
r @$t6 = 8
r @$t7 = 10
}
.foreach ($obj {!dumpheap -mt ${$arg1} -short})
{
$$ The !dumpheap -short option has last result as --------------- and
$$ this .if is to avoid this
.if ($spat ("${$obj}","------------------------------"))
{}
.else
{
$$ $t5 contains refernce to the array which has key and value for the
$$ session contents
r$t5 = poi(poi(poi(poi(${$obj}+@$t9)+@$t6)+@$t9)+@$t9)
$$$ Iterating through the array elements
.for (r $t0=0; @$t0 < poi(@$t5+@$t9); r$t0=@$t0+1 )
{
.if(@$t0 = 0)
{
$$ First occurence of the element in the array would be in the 20 offset for x64 and 10 offset for x86
r$t1=@$t7
}
.else
{
$$ the rest of the elements would be in the 8th offset for x64 and 4th offset for x86
r$t1= @$t7+(@$t0*@$t9)
}
$$ Check for null before trying to dump
.if (poi((@$t5-@$t9)+@$t1) = 0 )
{
.continue
}
.else
{
.echo ************
$$ Session Key
.printf /ow "Session Key is :- "; !ds poi(poi((@$t5-@$t9)+@$t1)+@$t9)
$$ Session value
.printf /ow "Session value is :- ";!ds poi(poi((@$t5-@$t9)+@$t1)+@$t6)
}
}
}
}
Copy the above script in to a file and invoke the script like this within Windbg
$$>a<”c:\Debuggersx86\dumpsession.txt” 000007fef4115c20
Passing the MT of System.Web.SessionState.HttpSessionState as the script argument.
Within the script I am using the alias !ds for dumping strings instead of using !dumpobj.
To create the alias use this command in x64
as !ds .printf "%mu \n", 10+
and in x86
as !ds .printf "%mu \n", C+
Replace !ds with !do for dumping regular objects instead of strings.
Here is the output from the above script
0:022> $$>a<”c:\Debuggersx86\dumpsession.txt” 000007fef4115c20
************
Session Key is :- Name
Session value is :- Test
************
Session Key is :- Name1
Session value is :- Test1
If you are only interested in getting the session contents the above script should get you the answer you are looking for. The rest of the post is an explanation of how the script works.
I am going to start by explaining one of the important statement in the script “r$t5 = poi(poi(poi(poi(${$obj}+@$t9)+@$t6)+@$t9)+@$t9)” which gets the contents of the array that contains the session key and value.
The $obj is the loop variable that contains the object address for each Http Session object “.foreach ($obj {!dumpheap -mt ${$arg1} -short})”
If I dump the Http session object using !do
0:022> !do 000000013fe20c30
Name: System.Web.SessionState.HttpSessionState
MethodTable: 000007fef4115c20
EEClass: 000007fef3d73e00
Size: 24(0×18) bytes
(C:\Windows\assembly\GAC_64\System.Web\2.0.0.0__b03f5f7f11d50a3a\System.Web.dll)
Fields:
MT Field Offset Type VT Attr Value Name
000007fef40b59e8 4001f59 8 …IHttpSessionState 0 instance 000000013fe20bc0 _container
We can see the 8th offset contains the pointer to the “_container” object in x64 and in x86 it will be the 4th offset and that’s the reason we use poi(${$obj}+@$t9) which should work for both x86 and x64 because the value of @$t9 is the pointer size which will be 4 in x86 and 8 in x64.
The next step is to dump the “_container” which is equal to poi(${$obj}+@$t9)
0:022> !do poi(000000013fe20c30+8)
Name: System.Web.SessionState.HttpSessionStateContainer
MethodTable: 000007fef411e868
EEClass: 000007fef3d77348
Size: 64(0×40) bytes
(C:\Windows\assembly\GAC_64\System.Web\2.0.0.0__b03f5f7f11d50a3a\System.Web.dll)
Fields:
MT Field Offset Type VT Attr Value Name
000007fef7b77a80 4001f5a 8 System.String 0 instance 0000000000000000 _id
000007fef4086508 4001f5b 10 …ateItemCollection 0 instance 000000013fe20458 _sessionItems
000007fef4115390 4001f5c 18 …ObjectsCollection 0 instance 000000013fe209d0 _staticObjects
000007fef7b7ecf0 4001f5d 28 System.Int32 1 instance 20 _timeout
000007fef7b76c50 4001f5e 34 System.Boolean 1 instance 1 _newSession
000007fef411f8c8 4001f5f 2c System.Int32 1 instance 1 _cookieMode
000007fef411f798 4001f60 30 System.Int32 1 instance 1 _mode
000007fef7b76c50 4001f61 35 System.Boolean 1 instance 0 _abandon
000007fef7b76c50 4001f62 36 System.Boolean 1 instance 0 _isReadonly
000007fef411e7b0 4001f63 20 …essionStateModule 0 instance 000000013fce1bd8 _stateModule
Now that we have the SessionContainer, we would have to get the contents of “_sessionItems” which is in the 10th offset in x64.
Next step is to dump “_sessionitems” using !do poi(poi(000000013fe20c30+8)+10) and this is equal to poi(poi(${$obj}+@$t9)+@$t6).In the starting of the script @$t6 is set to 10 or 8 based on platform.
0:022> !do poi(poi(000000013fe20c30+8)+10)
Name: System.Web.SessionState.SessionStateItemCollection
MethodTable: 000007fef4086650
EEClass: 000007fef3d2fcf0
Size: 112(0×70) bytes
(C:\Windows\assembly\GAC_64\System.Web\2.0.0.0__b03f5f7f11d50a3a\System.Web.dll)
Fields:
MT Field Offset Type VT Attr Value Name
000007fef7b76c50 400117b 44 System.Boolean 1 instance 0 _readOnly
000007fef7b7e968 400117c 8 …ections.ArrayList 0 instance 000000013fe20830 _entriesArray
000007fef7b7fd88 400117d 10 …IEqualityComparer 0 instance 000000013fc6b270 _keyComparer
000007fef7b7f3d8 400117e 18 …ections.Hashtable 0 instance 000000013fe20858 _entriesTable
000007fef6f6f938 400117f 20 …e+NameObjectEntry 0 instance 0000000000000000 _nullKeyEntry
000007fef6f479b8 4001180 28 …se+KeysCollection 0 instance 0000000000000000 _keys
000007fef7b66840 4001181 30 …SerializationInfo 0 instance 0000000000000000 _serializationInfo
000007fef7b7ecf0 4001182 40 System.Int32 1 instance 3 _version
000007fef7b77370 4001183 38 System.Object 0 instance 0000000000000000 _syncRoot
000007fef7bbd028 4001184 a70 …em.StringComparer 0 shared static defaultComparer
>> Domain:Value 00000000010e2690:NotInit 0000000002e0a0a0:00000000ffae8cb8 <<
000007fef7b76c50 4001f67 45 System.Boolean 1 instance 1 _dirty
000007fef4108b20 4001f68 48 …n+KeyedCollection 0 instance 0000000000000000 _serializedItems
000007fef7b7aa30 4001f69 50 System.IO.Stream 0 instance 0000000000000000 _stream
000007fef7b7ecf0 4001f6a 60 System.Int32 1 instance 0 _iLastOffset
000007fef7b77370 4001f6b 58 System.Object 0 instance 000000013fe20818 _serializedItemsLock
000007fef7b7f3d8 4001f66 18e0 …ections.Hashtable 0 shared static s_immutableTypes
>> Domain:Value 00000000010e2690:NotInit 0000000002e0a0a0:000000013fe204c8 <<
Next field that we are interested in is “_entriesArray” which is in the 8th offset in x64. To dump its contents here is the command !do
poi(poi(poi(000000013fe20c30+8)+10)+8) which is equal to poi(poi(poi(${$obj}+@$t9)+@$t6)+@$t9
0:022> !do poi(poi(poi(000000013fe20c30+8)+10)+8)
Name: System.Collections.ArrayList
MethodTable: 000007fef7b7e968
EEClass: 000007fef7781ee0
Size: 40(0×28) bytes
(C:\Windows\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Fields:
MT Field Offset Type VT Attr Value Name
000007fef7b65870 400094c 8 System.Object[] 0 instance 000000013fe3ddb8 _items
000007fef7b7ecf0 400094d 18 System.Int32 1 instance 2 _size
000007fef7b7ecf0 400094e 1c System.Int32 1 instance 2 _version
000007fef7b77370 400094f 10 System.Object 0 instance 0000000000000000 _syncRoot
000007fef7b65870 4000950 388 System.Object[] 0 shared static emptyArray
>> Domain:Value 00000000010e2690:00000000ffac6110 0000000002e0a0a0:00000000ffad19e0 <<
The next field we are interested is “_items” which is in the 8th offset in x64. Notice “_items” is an array and cannot be dumped using !dumpobj or !do. So this command “r$t5 = poi(poi(poi(poi(${$obj}+@$t9)+@$t6)+@$t9)+@$t9)” will set the array pointer to$t5.
Now that we have array containing the session items, we could have used !da to
dump the array contents with details using !da -details poi(poi(poi(poi(000000013fe20c30+8)+10)+8)+8)
0:022> !da -details poi(poi(poi(poi(000000013fe20c30+8)+10)+8)+8)
Name: System.Object[]
MethodTable: 000007fef7b65870
EEClass: 000007fef777eb58
Size: 64(0×40) bytes
Array: Rank 1, Number of elements 4, Type CLASS
Element Methodtable: 000007fef7b77370
[0] 000000013fe3dd98
Name: System.Collections.Specialized.NameObjectCollectionBase+NameObjectEntry
MethodTable: 000007fef6f6f938
EEClass: 000007fef6ce90b0
Size: 32(0×20) bytes
(C:\Windows\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll)
Fields:
MT Field Offset Type VT Attr Value Name
000007fef7b77a80 4001185 8 System.String 0 instance 000000013fe3dcf8 Key
000007fef7b77370 4001186 10 System.Object 0 instance 000000013fe3dd20 Value
[1] 000000013fe3ddf8
Name: System.Collections.Specialized.NameObjectCollectionBase+NameObjectEntry
MethodTable: 000007fef6f6f938
EEClass: 000007fef6ce90b0
Size: 32(0×20) bytes
(C:\Windows\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll)
Fields:
MT Field Offset Type VT Attr Value Name
000007fef7b77a80 4001185 8 System.String 0 instance 000000013fe3dd48 Key
000007fef7b77370 4001186 10 System.Object 0 instance 000000013fe3dd70 Value
[2] null
[3] null
But notice it does not help much because we still cannot see the actual key and value. That is the reason for using a nested “.for” loop in the script which will iterate through the array contents. FYI @$t5 contains reference to the array.
The statement “.for (r $t0=0; @$t0 < poi(@$t5+@$t9); r$t0=@$t0+1 )” is standard for loop with one thing that is special which is poi(@$t5+@$t9). The poi(@$t5+@$t9) contains the reference to the size of the array. How do I know that? The answer is dd poi(poi(poi(poi(000000013fe20c30+8)+10)+8)+8)
0:022> dd poi(poi(poi(poi(000000013fe20c30+8)+10)+8)+8)
00000001`3fe3ddb8 f7b65870 000007fe 00000004 00000000
00000001`3fe3ddc8 f7b77370 000007fe 3fe3dd98 00000001
00000001`3fe3ddd8 3fe3ddf8 00000001 00000000 00000000
00000001`3fe3dde8 00000000 00000000 00000000 00000000
00000001`3fe3ddf8 f6f6f938 000007fe 3fe3dd48 00000001
00000001`3fe3de08 3fe3dd70 00000001 00000000 00000000
00000001`3fe3de18 f7b77370 000007fe 00000000 00000000
00000001`3fe3de28 00000000 80000000 f7b77a80 000007fe
Notice the 8th offset value is 00000004 which is the size of the array and for
more information look at the post on custom dump array http://naveensrinivasan.com/2010/06/24/custom-dumparray-windbg/
The first element in the array would be in the 20th offset in x64 and 10th
offset in x86 and that is the reason for the “.if(@$t0=0)”
.if(@$t0 = 0)
{
$$ First occurence of the element in the array would be in the 20 offset for x64 and 10 offset for x86
r$t1=@$t7
}
So the first time the value @$t1 would be 20. And the rest of the elements would be in the 8th offset in x64 and 4th offset inx86
.else
{
$$ the rest of the elements would be in the 8th offset for x64 and 4th offset for x86
r$t1= @$t7+(@$t0*@$t9)
}
So the second time it would be 20+(1*8) = @$t7+(@$t0*@$t9) which will be 28th offset.
The next statement “.if (poi((@$t5-@$t9)+@$t1) = 0 )” is null check , this would avoid dumping an object which has not be initialized. This is because not all elements in the array could have been initialized.
The !ds poi(poi((@$t5-@$t9)+@$t1)+@$t9) gets the session key which is in the 8th offset (look at the previous output from dumparray) and the !ds poi(poi((@$t5-@$t9)+@$t1)+@$t6) gets the session value which is in the 10th offset.
Here is my initial x64 specific script that I wrote.
foreach ($obj {!dumpheap -mt ${$arg1} -short})
{
$$ The !dumpheap -short option has last result as --------------- and
$$ this .if is to avoid this
.if ($spat ("${$obj}","------------------------------"))
{}
.else
{
$$ $t5 contains reference to the array which has key and value for the
$$ session contents
r$t5 = poi(poi(poi(poi(${$obj}+0x8)+0x10)+0x8)+0x8);
r$t1 = 0
.for (r $t0=0; @$t0 < poi(@$t5+0x8); r$t0=@$t0+1 )
{
.if(@$t0 = 0)
{
$$ First occurrence of the element in the array would be in the 20 offset
r$t1=20
}
.else
{
$$ the rest of the elements would be in the 8th offset
r$t1= 20+(@$t0*8)
};
$$ Check for null before trying to dump
.if (poi((@$t5-0x8)+@$t1) = 0 )
{
.continue
}
.else
{
.echo ************;
? @$t0
$$ Session Key
.printf "Session Key is :- "; !ds poi(poi((@$t5-0x8)+@$t1)+0x8);
$$ Session value
.printf "Session value is :- ";!ds poi(poi((@$t5-0x8)+@$t1)+0x10)
}
}
}
}
I had fun writing this script. Let me know if there is a better way to write this.
Script to load sos within Windbg based on .NET Framework version
I often debug .NET Framework v 2.0 / v 4.0 code within windbg. In v 2.0 the main clr dll was called “mscorwks.dll” and in v 4.0 it is called “clr.dll”. As many of you are aware , to load sos in v 2.0 we would have to enter “.loadby sos mscorwks” and in v 4.0 it would be “.loadby sos clr” . This was a pain for me. Came up with a script to automate loading sos based on clr version
!for_each_module .if(($sicmp( "@#ModuleName" , "mscorwks") = 0) ) {.loadby sos mscorwks} .elsif ($sicmp( "@#ModuleName" , "clr") = 0) {.loadby sos clr}
You can take it up a notch by setting a break-point within clr based on the .NET Framework version
!for_each_module .if(($sicmp( "@#ModuleName" , "mscorwks") = 0) ) {bp mscorwks!WKS::GCHeap::SuspendEE ".if (dwo(mscorwks!WKS::GCHeap::GcCondemnedGeneration)==2) {.echo start of gen 2}"} .elsif ($sicmp( "@#ModuleName" , "clr") = 0) {bp clr!WKS::GCHeap::SuspendEE ".if (dwo(clr!WKS::GCHeap::GcCondemnedGeneration)==2) {.echo start of gen 2}"}
Debugging Generic System.Nullable within Windbg
In this post I am going to unravel the mystery of debugging the Nullable<T> within Windbg in .NET 3.5 and also compare it with .NET 4.0. Here is the sample code and it is compiled in .NET 3.5
using System;
namespace ConsoleApplication
{
class Program
{
Int32? test;
int i = 10;
static void Main(string[] args)
{
Nullable<T>
Int32? i = 10;
Object o = 10;
var p = new Program() { test = 20 };
Console.Read();
p.test = (Int32?) o ;
Console.WriteLine(p.test.HasValue);
}
}
}
Attached to the debugger on the Console.Read. FYI I always load sos and sosex extensions to debug managed code. Here is !mdt 0x0253c11c output
0:000> !mdt 0x0253c11c
0253c11c (ConsoleApplication.Program)
test:ERROR (0×80070057).
i:0xa (System.Int32)
Notice that “test” does not have a value and has an error. Next issued !dumpobj
!do 0x0253c11c
0:000> !do 0x0253c11c
Name: ConsoleApplication.Program
MethodTable: 002932f0
EEClass: 00291360
Size: 20(0×14) bytes
(C:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication9\bin\Debug\ConsoleApplication.exe)
Fields:
MT Field Offset Type VT Attr Value Name
00000000 4000001 8 1 instance 0280c124 test
7776ab0c 4000002 4 System.Int32 1 instance 10 i
My fault ,I thought sos should be able to get the MethodTable of Nullable<Int32> for “test” when sosex couldn’t. To my surprise the MT output was 00000000 . To view the contents of the “test” I would have to use the !dumpvc which requires methodtable. I know I could use the dd command. And here is the output from the dd 0280c124
0:000> dd 0280c124
0280c124 00000001 00000014 00000000 77767c70
0280c134 00000000 00000000 00000000 00000000
0280c144 00000000 00000000 777684dc 00000000
0280c154 40010000 7776d7ec 00000003 00000008
0280c164 00000100 00000000 77767cc4 00000000
0280c174 00000000 00000000 00000000 00000001
0280c184 0280c158 00000001 00000000 7776841c
0280c194 00000000 00000000 00000000 00000000
The second field 00000014 is the actual value of test and here is the actual output
0:000> ? poi(0280c124+0×4)
Evaluate expression: 20 = 00000014
But this does not solve the real issue of figuring out the methodtable to use it in !dumpvc. I could have used !mx System.Nullable* to get the MethodTable, because I knew the type is Nullable<Int> ,what if I didn’t know the type information.
To get the mt information I had to disassemble the code. First step is to get the !clrstack
0:000> !CLRStack
OS Thread Id: 0x94c (0)
ESP EIP
0021f1dc 769d73ea [NDirectMethodFrameStandaloneCleanup: 0021f1dc] System.IO.__ConsoleStream.ReadFile(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)
0021f1f8 77c8ae67 System.IO.__ConsoleStream.ReadFileNative(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte[], Int32, Int32, Int32, Int32 ByRef)
0021f224 77c8ad86 System.IO.__ConsoleStream.Read(Byte[], Int32, Int32)
0021f244 776f9fbb System.IO.StreamReader.ReadBuffer()
0021f258 77c677fc System.IO.StreamReader.Read()
0021f264 77c8dd81 System.IO.TextReader+SyncTextReader.Read()
0021f270 77bd328b System.Console.Read()
0021f278 00320115 ConsoleApplication.Program.Main(System.String[])
0021f4d0 59781b6c [GCFrame: 0021f4d0]
The next is to !u 00320115 and here is the partial ouput
00320116 8b45d8 mov eax,dword ptr [ebp-28h]
00320119 3a4008 cmp al,byte ptr [eax+8]
0032011c 8d4008 lea eax,[eax+8]
0032011f 8945c8 mov dword ptr [ebp-38h],eax
00320122 ff75dc push dword ptr [ebp-24h]
00320125 8b4dc8 mov ecx,dword ptr [ebp-38h]
00320128 bae8397777 mov edx,offset mscorlib_ni+0x2739e8 (777739e8) (MT: System.Nullable`1[[System.Int32, mscorlib]])
0032012d e8d6a74c59 call mscorwks!JIT_Unbox_Nullable (597ea908)
Notice the Method table 777739e8 for System.Nullable`1[[System.Int32, mscorlib]] and here is the output from !dumpmt -md 777739e8
0:000> !dumpmt -md 777739e8
EEClass: 7752e7c8
Module: 77501000
Name: System.Nullable`1[[System.Int32, mscorlib]]
mdToken: 0200026d (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
BaseSize: 0×10
ComponentSize: 0×0
Number of IFaces in IFaceMap: 0
Slots in VTable: 14
————————————–
MethodDesc Table
Entry MethodDesc JIT Name
77697028 775eace0 NONE System.Nullable`1[[System.Int32, mscorlib]].ToString()
77697020 775eacc0 NONE System.Nullable`1[[System.Int32, mscorlib]].Equals(System.Object)
77697018 775eacd0 NONE System.Nullable`1[[System.Int32, mscorlib]].GetHashCode()
777374c0 775412a4 PreJIT System.Object.Finalize()
77d010a0 775eac98 PreJIT System.Nullable`1[[System.Int32, mscorlib]]..ctor(Int32)
77d01100 775eaca0 PreJIT System.Nullable`1[[System.Int32, mscorlib]].get_HasValue()
77d01120 775eaca8 PreJIT System.Nullable`1[[System.Int32, mscorlib]].get_Value()
77d01030 775eacb0 PreJIT System.Nullable`1[[System.Int32, mscorlib]].GetValueOrDefault()
77d01140 775eacb8 PreJIT System.Nullable`1[[System.Int32, mscorlib]].GetValueOrDefault(Int32)
77d0100c 775eacf0 PreJIT System.Nullable`1[[System.Int32, mscorlib]].op_Implicit(Int32)
77d00fe8 775eacf8 PreJIT System.Nullable`1[[System.Int32, mscorlib]].op_Explicit(System.Nullable`1<Int32>)
77d01040 775eacc8 PreJIT System.Nullable`1[[System.Int32, mscorlib]].Equals(System.Object)
77d01078 775eacd8 PreJIT System.Nullable`1[[System.Int32, mscorlib]].GetHashCode()
77d010c0 775eace8 PreJIT System.Nullable`1[[System.Int32, mscorlib]].ToString()
Now that I have confirmed the mt and here is the output from !dumpvc 777739e8 0280c124
0:000> !dumpvc 777739e8 0280c124
Name: System.Nullable`1[[System.Int32, mscorlib]]
MethodTable 777739e8
EEClass: 7752e7c8
Size: 16(0×10) bytes
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Fields:
MT Field Offset Type VT Attr Value Name
7776eadc 40009a8 0 System.Boolean 1 instance 1 hasValue
7776ab0c 40009a9 4 System.Int32 1 instance 20 value
I decided to test this in .NET 4.0 and here is the output of !mdt for the Program object
0:000> !mdt 0x0236bc44
0236bc44 (ConsoleApplication.Program)
test:(System.Nullable`1[[System.Int32, mscorlib]]) VALTYPE (MT=6229f60c, ADDR=0236bc50)
i:0xa (System.Int32)
Notice the “test” which is Nullable<Int32> is now recognized by sosex and it also provides the method table.
Exploring UnhandledException in .NET and Watson buckets
I wanted to understand about UnhandledExceptions in .NET because of the few questions that I saw in the CLR Forum ,which had watson buckets in the event viewer. To get deep understanding unhandled exception filter there is a article on MSDN from CLR Team.
In this post I will be demonstrating how to get the Watson Bucket from within your code . FYI this is the same information you can get !WatsonBuckets from sos within Windbg whenever there is a termination of the .NET process. !WatsonBuckets is undocumented. I am using the CLR hosting interfaces to get the watson bucket. Here is the code that throws an unhandled exception and invokes clr to get watson bucket info.
using System;
using System.Runtime.InteropServices;
namespace WatsonTest
{
internal class Test
{
private static void Main(string[] args)
{
var t = AppDomain.CurrentDomain;
t.UnhandledException += (s, e) =>
{
var c = GetWatsonBuckets();
Console.WriteLine(c);
Console.Read();
};
throw new NullReferenceException();
}
private static WatsonBuckets GetWatsonBuckets()
{
var pParams = new WatsonBuckets();
IClrRuntimeHost host = null;
host = Activator.CreateInstance(Type.GetTypeFromCLSID(ClrGuids.ClsIdClrRuntimeHost)) as IClrRuntimeHost;
if (host != null)
{
var clrControl = host.GetCLRControl();
if (clrControl == null)
{
return pParams;
}
var clrErrorReportingManager =
clrControl.GetCLRManager(ref ClrGuids.IClrErrorReportingManager) as IClrErrorReportingManager;
if (clrErrorReportingManager == null)
{
return pParams;
}
clrErrorReportingManager.GetBucketParametersForCurrentException(out pParams);
}
return pParams;
}
}
// BucketParameters Structure to get watson buckets back from CLR
//http://msdn.microsoft.com/en-us/library/ms404466(v=VS.100).aspx
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct WatsonBuckets
{
internal int fInited;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0xff)] internal string pszEventTypeName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0xff)] internal string param0;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0xff)] internal string param1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0xff)] internal string param2;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0xff)] internal string param3;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0xff)] internal string param4;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0xff)] internal string param5;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0xff)] internal string param6;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0xff)] internal string param7;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0xff)] internal string param8;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0xff)] internal string param9;
}
internal static class ClrGuids
{
internal static readonly Guid ClsIdClrRuntimeHost = new Guid("90F1A06E-7712-4762-86B5-7A5EBA6BDB02");
internal static Guid IClrErrorReportingManager = new Guid("980D2F1A-BF79-4c08-812A-BB9778928F78");
internal static readonly Guid IClrRuntimeHost = new Guid("90F1A06C-7712-4762-86B5-7A5EBA6BDB02");
}
[Guid("90F1A06C-7712-4762-86B5-7A5EBA6BDB02"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IClrRuntimeHost
{
void Start();
void Stop();
void SetHostControl(IntPtr pHostControl);
IClrControl GetCLRControl();
void UnloadAppDomain(int dwAppDomainId, bool fWaitUntilDone);
void ExecuteInAppDomain(int dwAppDomainId, IntPtr pCallback, IntPtr cookie);
int GetCurrentAppDomainId();
int ExecuteApplication(string pwzAppFullName, int dwManifestPaths, string[] ppwzManifestPaths,
int dwActivationData, string[] ppwzActivationData);
int ExecuteInDefaultAppDomain(string pwzAssemblyPath, string pwzTypeName, string pwzMethodName,
string pwzArgument);
}
[Guid("9065597E-D1A1-4fb2-B6BA-7E1FCE230F61"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IClrControl
{
[return: MarshalAs(UnmanagedType.IUnknown)]
object GetCLRManager([In] ref Guid riid);
void SetAppDomainManagerType(string pwzAppDomainManagerAssembly, string pwzAppDomainManagerType);
}
// IClrErrorReportingManager to get watson bukets back from CLR
//http://msdn.microsoft.com/en-us/library/ms164367(v=VS.100).aspx
[Guid("980D2F1A-BF79-4c08-812A-BB9778928F78"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IClrErrorReportingManager
{
[PreserveSig]
int GetBucketParametersForCurrentException(out WatsonBuckets pParams);
}
}
The watson bucket information has ten items
- AppName
- Version
- ?
- Assembly and Module Name
- Assembly Version
- ?
- MethodDef
- IL Offset
- Exception Type
- ?
In the above code WatsonBuckets is a structure to get watson bucket information back from CLR. In the code I use 3 hosting interfaces, IClrRuntimeHost which is the main interface, the next interface is IClrControl which has GetCLRManager method to get ErrorReportingManger. IClrErrorReportingManager which has the method to get the exception buckets which is GetBucketParametersForCurrentException. I know that there isn’t going to be much of use for this code. But it gives me better understanding of CLR integrates with watson when ever there is termination of an application.
Exploring SOSEX and Windbg to debug .NET 4.0
With the latest release of sosex comes a new set of functions to debug. It is pretty awesome that one person (Steve) alone could pull of such cool things. In this blog post, I am just going to demonstrate how easy it is to debug managed code using sosex compared to sos.
using System;
using System.Collections.Generic;
namespace MemCheck
{
internal class Test
{
Dictionary<int, string> dict = new Dictionary<int, string>();
private static void Main(string[] args)
{
var p = new Test();
for (int i = 0; i < 100; i++)
{
p.dict.Add(i, i.ToString());
}
Console.WriteLine("Done");
Console.Read();
}
}
}
I like to keep the code simple , so it is easy to follow. The debugging goal for today is to get the Dictionary values. First I am going to demonstrate it using sos and then using sosex.
As usual I start the app and then attach it to windbg.
.loadby sos clr
FYI in .net 4.0 clr is the dll that has CLR implementation. In prior versions it used to be in mscorwks. The next command would look for the object Test in the memory
!dumpheap -type MemCheck.Test 0:000> !dumpheap -type MemCheck.Test Address MT Size 0000000002761e20 000007ff00054110 24 total 0 objects Statistics: MT Count TotalSize Class Name 000007ff00054110 1 24 MemCheck.Test Total 1 objects
The next step is to dump the object
0:000> !do 0000000002761e20 Name: MemCheck.Test MethodTable: 000007ff00054110 EEClass: 000007ff00162350 Size: 24(0x18) bytes File: C:\Users\naveen\Documents\Visual Studio 2010\Projects\Test\bin\Debug\Test.exe Fields: MT Field Offset Type VT Attr Value Name 000007feec2b7a48 4000001 8 ...tring, mscorlib]] 0 instance 0000000002761e38 dict
Notice the dict object is in the 8th offset . To dump contents dict object I would use the command !do poi(0000000002761e20+8) , which is pointer deference of Test object on it is 8th offset. And here is the output
0:000> !do poi(0000000002761e20+8) Name: System.Collections.Generic.Dictionary`2[[System.Int32, mscorlib],[System.String, mscorlib]] MethodTable: 000007feec2b7a48 EEClass: 000007feebe113c0 Size: 88(0x58) bytes File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll Fields: MT Field Offset Type VT Attr Value Name 000007feec27c7d8 4000bee 8 System.Int32[] 0 instance 0000000002764788 buckets 000007feecbd3dc8 4000bef 10 ...non, mscorlib]][] 0 instance 0000000002764ab8 entries 000007feec27c848 4000bf0 40 System.Int32 1 instance 100 count 000007feec27c848 4000bf1 44 System.Int32 1 instance 100 version 000007feec27c848 4000bf2 48 System.Int32 1 instance -1 freeList 000007feec27c848 4000bf3 4c System.Int32 1 instance 0 freeCount 000007feec2a5a48 4000bf4 18 ...Int32, mscorlib]] 0 instance 0000000002761ef0 comparer 000007feecc75f78 4000bf5 20 ...Canon, mscorlib]] 0 instance 0000000000000000 keys 000007feecc72078 4000bf6 28 ...Canon, mscorlib]] 0 instance 0000000000000000 values 000007feec275ab8 4000bf7 30 System.Object 0 instance 0000000000000000 _syncRoot 000007feec29a1b8 4000bf8 38 ...SerializationInfo 0 instance 0000000000000000 m_siInfo
And the dictionary object in turn stores them within an array which is again the 8th offset. This time because we know it is an array we are going to use the !dumparray command on the memory location. The command to get the details is
!dumparray -details poi(poi(0000000002761e20+8)+8) MT Field Offset Type VT Attr Value Name 000007feec27c848 400047b 0 System.Int32 1 instance -1 m_value [195] 0000000002764aa4 Name: System.Int32 MethodTable: 000007feec27c848 EEClass: 000007feebe00890 Size: 24(0x18) bytes File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll Fields: MT Field Offset Type VT Attr Value Name 000007feec27c848 400047b 0 System.Int32 1 instance -1 m_value [196] 0000000002764aa8 Name: System.Int32 MethodTable: 000007feec27c848 EEClass: 000007feebe00890 Size: 24(0x18) bytes File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll Fields: MT Field Offset Type VT Attr Value Name 000007feec27c848 400047b 0 System.Int32 1 instance -1 m_value
Here is the partial output.
Now lets try and do the same thing using sosex. The one thing that I really like about the new sosex is that I can use names rather than pointer deference which is way much easier. Launched the app and then loaded sosex using the command
.load F:\Work\Tools\debuggers\sosex.dll
and then switched the thread from 4th to 0th thread using ~0s. By default the debugger injects a thread into the process for debugging and that was the 4th thread. The next command I issued was to get stack trace
!mk 0:000> !mk Thread 0: ESP EIP 00:U 000000000015e408 0000000077bc00da ntdll!ZwRequestWaitReplyPort+0xa 01:U 000000000015e410 0000000077a72b08 KERNEL32!ConsoleClientCallServer+0x54 02:U 000000000015e440 0000000077aa5601 KERNEL32!ReadConsoleInternal+0x1f1 03:U 000000000015e590 0000000077aba922 KERNEL32!ReadConsoleA+0xb2 04:U 000000000015e670 0000000077a89934 KERNEL32!zzz_AsmCodeRange_End+0x8bea 05:U 000000000015e6b0 000007feed0317c7 clr!DoNDirectCall__PatchGetThreadCall+0x7b 06:M 000000000015e760 000007feec1d34a1 DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)(+0x0 IL)(+0x0 Native) 07:M 000000000015e880 000007feec97f59a System.IO.__ConsoleStream.ReadFileNative(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte[], Int32, Int32, Int32, Int32 ByRef)(+0x53 IL)(+0xba Native) 08:M 000000000015e8f0 000007feec97f402 System.IO.__ConsoleStream.Read(Byte[], Int32, Int32)(+0x5d IL)(+0x62 Native) 09:M 000000000015e950 000007feec18e63c System.IO.StreamReader.ReadBuffer()(+0xa0 IL)(+0x5c Native) 0a:M 000000000015e9a0 000007feec915630 System.IO.StreamReader.Read()(+0x21 IL)(+0x30 Native) 0b:M 000000000015e9e0 000007feec987458 System.IO.TextReader+SyncTextReader.Read()(+0x0 IL)(+0x38 Native) 0c:M 000000000015ea30 000007ff00170213 MemCheck.Test.Main(System.String[])(+0x39 IL)(+0xf3 Native) [C:\Users\naveen\Documents\Visual Studio 2010\Projects\Test\Program.cs, @ 17,13] 0d:U 000000000015eaa0 000007feed0710b4 clr!CallDescrWorker+0x84 0e:U 000000000015eaf0 000007feed0711c9 clr!CallDescrWorkerWithHandler+0xa9 0f:U 000000000015eb70 000007feed071245 clr!MethodDesc::CallDescr+0x2a1 10:U 000000000015eda0 000007feed171675 clr!ClassLoader::RunMain+0x228 11:U 000000000015eff0 000007feed1717ac clr!Assembly::ExecuteMainMethod+0xac 12:U 000000000015f2a0 000007feed171562 clr!SystemDomain::ExecuteMainMethod+0x452 13:U 000000000015f850 000007feed173dd6 clr!ExecuteEXE+0x43 14:U 000000000015f8b0 000007feed173cf3 clr!CorExeMainInternal+0xc4 15:U 000000000015f920 000007feed1f7365 clr!CorExeMain+0x15 16:U 000000000015f960 000007fef8f13309 mscoreei!CorExeMain+0x41 17:U 000000000015f990 000007fef8fa5b21 MSCOREE!CorExeMain_Exported+0x57 18:U 000000000015f9c0 0000000077a6f56d KERNEL32!BaseThreadInitThunk+0xd 19:U 000000000015f9f0 0000000077ba3281 ntdll!RtlUserThreadStart+0x1d
FYI the command !mk has been part of sos from the initial version. I am interested in only looking at the code that I wrote so I would like to move stack frame to 0c which is MemCheck.Test.Main . To do that the command is !mframe 0c, which moves to that stackframe. The reason to move the particular stack frame is to look for variables in the stack and the command to variables is !mdv , which display managed local variables
0:000> !mdv Frame 0xc: (MemCheck.Test.Main(System.String[])): [A0]:args:0x0000000002761dd8 (System.String[]) [L0]:p:0x0000000002761e20 (MemCheck.Test) [L1]:i:0x0000000000000064 (System.Int32) [L2]:CS$4$0000:0x0000000000000000 (System.Boolean)
Notice we see the local variable “p” which is of type MemCheck.Test. To display type p we issue the command !mdt p
0:000> !mdt p 0000000002761e20 (MemCheck.Test) dict:0000000002761e38 (System.Collections.Generic.Dictionary`2[[System.Int32, mscorlib],[System.String, mscorlib]])
I didn’t have to get memory address , I am using the names which is very intuitive ,especially when we have to debug large application with N levels of nesting.So to get the dict values from p the command to issue is !mdt -e p.dict
!mdt -e p.dict [98] (System.Collections.Generic.Dictionary`2+Entry[[System.Int32, mscorlib],[System.String, mscorlib]]) VALTYPE (MT=000007feec2b7b28, ADDR=0000000002765400) key:0x62 (System.Int32) value:0000000002765e48 (System.String: "98") [99] (System.Collections.Generic.Dictionary`2+Entry[[System.Int32, mscorlib],[System.String, mscorlib]]) VALTYPE (MT=000007feec2b7b28, ADDR=0000000002765418) key:0x63 (System.Int32) value:0000000002765e68 (System.String: "99")
Here is the partial output. Notice I never had to use a memory pointer or do a pointer deference .This is very similar to VS.NET debugging where I am used to the variable names compared the memory address. Thanks to Steve for providing such a cool extension.
Debugging .Net framework source code within Windbg
One the coolest thing Microsoft did was to release the .NET Framework source code. In this post, I am going to demonstrate, how we could have a break-point on the .NET framework source code by line numbers ,using Windbg ,very similar to doing in VS.NET.
The first step towards doing this is to download .NET Framework Source Code and installing it on the local machine. The next step is to set the symbol path environment variable. My _NT_SYMBOL_PATH is set to
SRV*d:\dev\symbols*http://referencesource.microsoft.com/symbols; SRV*d:\dev\symbols*http://msdl.microsoft.com/download/symbols
Setting the correct symbol path is important to download symbols from MS.
Here is the source code that I would be using to demonstrate this
using System;
using System.Net;
namespace Test {
class Program {
static void Main(string[] args) {
Console.WriteLine("Hello World of debugging");
var wr = WebRequest.Create("http://www.google.com");
Console.WriteLine("Web request created");
var req = wr.GetRequestStream();
Console.WriteLine("Hello World Debugging");
Console.Read();
}
}
}
I am going to demonstrate the same thing using multiple versions of debugger. The first one that I am going to demonstrate is using Windows Debugger Version 6.12.0002.633 X86 which is the latest version.
Launched the exe within windbg and opened the source code WebRequest.cs and Program.cs withing Windbg.
The WebRequest.cs is the source code that was downloaded from MS Reference Source code and Program.cs is the above sample code.
Then issued the command, to be notified when mscorlib is loaded
sxe ld:mscorlib
And here is the output from the above command
ModLoad: 53fd0000 54ac8000 C:\Windows\assembly\NativeImages_v2.0.50727_32\mscorlib\8c1770d45c63cf5c462eeb945ef9aa5d\mscorlib.ni.dll
eax=00000000 ebx=00000000 ecx=00000000 edx=00000000 esi=7efdd000 edi=0040eaf4
eip=7723fc02 esp=0040e9c8 ebp=0040ea1c iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
ntdll!ZwMapViewOfSection+0×12:
7723fc02 83c404 add esp,4
then issued the following commands to load by sos, sosex and set break-points
.loadby sos mscorwks .load sosex !mbm System.Net.WebRequest.Create !mbp WebRequest.cs 98
So with the above command I am requesting for a break-point on the method System.Net.WebRequest.Create using symbol (!mbm). I am issuing the command !mbm ,just so that sosex can hook up with CLR for getting notifications on JIT. Without this I was unable to set break-point on source code using line numbers. The next command !mbp WebRequest.cs 98 means, have a break-point on the line number 98 in the WebRequest.cs file . The line 98 contents are “if (!useUriBase)” . I forgot to mention the .NET framework 4.0 source code is not released so I am using 3.5
Also make sure that the correct private pdb symbols are loaded for framework assemblies, and to verify that, issue the command “lme” and the output should contain something like this
0:000> lme
start end module name
013b0000 013b8000 ConsoleApplication1 C (private pdb symbols) C:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication1\bin\Debug\ConsoleApplication1.pdb
53fd0000 54ac8000 mscorlib_ni C (private pdb symbols) d:\dev\symbols\mscorlib.pdb\F85F3DD0C7024D528B4C37F1ACF2123D1\mscorlib.pdb
58930000 590c9000 System_ni C (private pdb symbols) d:\dev\symbols\System.pdb\97A082CB5BC64B30887253632D3901EE1\System.pdb
And here is the output after letting it run
Breakpoint 1 hit
eax=00000001 ebx=0040f37c ecx=0282c160 edx=00000000 esi=008ba398 edi=0282c160
eip=58dec8b7 esp=0040f32c ebp=0040f334 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
System_ni+0x4bc8b7:
58dec8b7 85ff
then issued the commands to see the list of break-points (managed and native)
!mbl bl
and here is the output
0:000> !mbl
0 e : *!SYSTEM.NET.WEBREQUEST.CREATE ILOffset=0: pass=1 oneshot=false thread=ANY
System!System.Net.WebRequest.Create(Uri, bool)+0xfffffffe(IL)
0 e 58a71630
System!System.Net.WebRequest.Create(string)
1 e 58dec8b7
System!System.Net.WebRequest.Create(Uri)+0xfffffffe(IL)
2 e 58a71600
1 eu: WebRequest.cs, line 98: pass=1 oneshot=false thread=ANY
0:000> bl
0 e 58a71630 0001 (0001) 0:**** System_ni+0×141630
1 e 58dec8b7 0001 (0001) 0:**** System_ni+0x4bc8b7
2 e 58a71600 0001 (0001) 0:**** System_ni+0×141600
because Webrequest.Create has two overloads, sosex has set a break-point on both these methods and that’s the reason we are seeing 3 break-points, instead of 2. And after issuing the “g” command for couple of hits for the Webrequest.Create , then comes the Breakpoint 3 hit. Voila ,we have the managed to set break-point on framework source code using line numbers and here is the output of !mk command
Breakpoint 3 hit
eax=00000000 ebx=0014ecdc ecx=0282dde4 edx=00000000 esi=00000000 edi=00000000
eip=58a71650 esp=0014ec68 ebp=0014ec84 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
System_ni+0×141650:
58a71650 8bc7 mov eax,edi
0:000> !mk
ESP RetAddr
00:M 0014ec68 58a71650 System.Net.WebRequest.Create(System.Uri, Boolean)(+0×23 IL)(+0×0 Native) [f:\dd\ndp\fx\src\Net\System\Net\WebRequest.cs, @ 96,13]
01:M 0014ec8c 58dec8dd System.Net.WebRequest.Create(System.String)(+0×14 IL)(+0×26 Native)
02:M 0014ec9c 005000a9 Test.Program.Main(System.String[])(+0xc IL)(+0×18 Native) [C:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication1\Program.cs, @ 7,13]
03:U 0014ecb8 6cfb1b6c mscorwks!CallDescrWorker+0×33
04:U 0014ecc8 6cfc2209 mscorwks!CallDescrWorkerWithHandler+0xa3
05:U 0014ed48 6cfd6511 mscorwks!MethodDesc::CallDescr+0x19c
06:U 0014ee8c 6cfd6544 mscorwks!MethodDesc::CallTargetWorker+0x1f
07:U 0014eea8 6cfd6562 mscorwks!MethodDescCallSite::CallWithValueTypes_RetArgSlot+0x1a
08:U 0014eec0 6d040c45 mscorwks!ClassLoader::RunMain+0×223
09:U 0014f024 6d040b65 mscorwks!Assembly::ExecuteMainMethod+0xa6
0a:U 0014f28c 6d0410b5 mscorwks!SystemDomain::ExecuteMainMethod+0×456
0b:U 0014f75c 6d04129f mscorwks!ExecuteEXE+0×59
0c:U 0014f7ac 6d0411cf mscorwks!_CorExeMain+0x15c
0d:U 0014f7f4 73a461f0 mscoreei!_CorExeMain+0×38
0e:U 0014f800 74337f16 MSCOREE!ShellShim__CorExeMain+0×99
0f:U 0014f810 74334de3 MSCOREE!_CorExeMain_Exported+0×8
10:U 0014f818 74f73677 KERNEL32!BaseThreadInitThunk+0xe
11:U 0014f824 77259d72 ntdll!__RtlUserThreadStart+0×70
12:U 0014f864 77259d45 ntdll!_RtlUserThreadStart+0x1b
Notice on the top frame of the stack (00:M) we see source information. The advantage of this is, for example I can check the values of the local variable with in the function after certain calls ,which wouldn’t have been possible without jumping hoops. Here is the the output !mdv after the line breakpoint
0:000> !mdv
Frame 0×0: (System.Net.WebRequest.Create(System.Uri, Boolean)):
[A0]:requestUri:0x282d2fc (System.Uri)
[A1]:useUriBase:0×0 (System.Boolean)
[L0]:LookupUri:<?>
[L1]:Current:null (System.Net.WebRequestPrefixElement)
[L2]:Found:0×0 (System.Boolean)
[L3]:LookupLength:<?>
[L4]:prefixList:<?>
[L5]:i:<?>
[L6]:webRequest:<?>
And my next quest was to figure What if I could use the actual break-point on the source code directly instead of using sosex. So chose program.cs and hit the F9 key on the second Console.WriteLine and the color changed to red
And here is the output o f the bl command
0:000> bl
0 e 58a71630 0001 (0001) 0:**** System_ni+0×141630
1 e 58dec8b7 0001 (0001) 0:**** System_ni+0x4bc8b7
2 e 58a71600 0001 (0001) 0:**** System_ni+0×141600
3 e 58a71650 0001 (0001) 0:**** System_ni+0×141650
4 e 013b0017 0001 (0001) 0:**** ConsoleApplication1!Main+0×17
Notice there is a new break-point 4. But bad luck, when i let it run it didn’t work and I had to clear the break-point 4 . It failed to create a break-point and here is the output
0:000> g
Unable to insert breakpoint 4 at 013b0017, Win32 error 0n998
“Invalid access to memory location.”
bp4 at 013b0017 failed
WaitForEvent failed
eax=00000000 ebx=0014ecdc ecx=0282dde4 edx=00000000 esi=00000000 edi=00000000
eip=58a71650 esp=0014ec68 ebp=0014ec84 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000346
System_ni+0×141650:
58a71650 8bc7 mov eax,edi
The next step was to debug the with the version of debugger which had capabilities to see managed call stacks, Windbg version 6.7.5.0. Did the same thing loaded up the exe and the Program.cs in to the debugger and set a break-point on mscorlib load using ” sxe ld:mscorlib” and then issued the command “.loadby sos mscorwks” ,”.load sosex” and “!mbm System.Net.WebreRequest.Create”. And the let it run, and to my surprise here is the integrated debugging experience, the source code is highlighted, when break-point is hit for WebRequest.Create
And after hitting F10 ,it moved to the next line
Wow this is way cool. The next step was to set a break-point using the bp command on the Program.cs
bp (@@masm(`Program.cs:8+`))
And the break-point was hit and I was successfully able to have Integrated debugging environment. I know most of us don’t have Windbg version 6.7.5.0 , but there are few debugging geeks who still have them. I was happy that I kept the version.
Take Away
We should be able to debug within the framework source code using the latest version of debugger and if you have 6.7.5.0 , can have integrated debugging experience similar to VS.NET .
Using windbg as a Sql profiler in .NET
Most of the times when I am debugging using Windbg, I end up debugging code which has interaction with the Sql server database. So to troubleshoot what is being passed to Sql server I would have to fire up a Sql profiler or open the application log file. Wouldn’t be easy just to use one tool and like the new vs2010 which has Sql server execute reader command text shown (intellitrace) in the debugger window.
This is not hard using Windbg . This is a simple trick which can help in every developer be more productive. And here is the script for getting the Sql Command text on the call to execute reader
bp 000007fee174df30 ".printf \"\\n%mu\", poi(@rcx+20)+c ;g"
on a 64-bit machine. The address to Sql command Execute reader is not going to change because it is ngened 000007fee174df30. I am going to show how to get this information ,so that the same technique can be used to for debugging other code.Here are the steps
!dumpheap -type SqlCommand
0:009> !dumpheap -type SqlCommand
Address MT Size
0000000002722e70 000007fee1d6dae0 224
0000000002723618 000007fee1d75d40 56
total 0 objects
Statistics:
MT Count TotalSize Class Name
000007fee1d75d40 1 56 System.Data.SqlClient.SqlCommand+CachedAsyncState
000007fee1d6dae01 224 System.Data.SqlClient.SqlCommand
Total 2 objects
.shell -ci "!dumpmt -md 000007fee1d6dae0 " findstr ExecuteReader
000007fee1cf2450 000007fee1be2078 PreJIT System.Data.SqlClient.SqlCommand.BeginExecuteReader(System.AsyncCallback, System.Object)
000007fee1cedf30 000007fee1be20a0 PreJIT System.Data.SqlClient.SqlCommand.ExecuteReader()
000007fee1cedfb0 000007fee1be20b8 PreJIT System.Data.SqlClient.SqlCommand.ExecuteReader(System.Data.CommandBehavior)
000007fee1cf2460 000007fee1be20d0 PreJIT System.Data.SqlClient.SqlCommand.BeginExecuteReader(System.Data.CommandBehavior)
So the function address is 000007fee1cedf30 and this is where we would need to have a break-point on. Now that we have the address of the function, we would have to know the field offset of the command text so that we can dump the text on to the debugger automatically every time the break-point hits. To do that we would have to dump the contents of the SqlCommand object.
!do 0000000002722e70
0:009> !do 0000000002722e70
Name: System.Data.SqlClient.SqlCommand
MethodTable: 000007fee1d6dae0
EEClass: 000007fee1bf1840
Size: 224(0xe0) bytes
File: C:\Windows\Microsoft.Net\assembly\GAC_64\System.Data\v4.0_4.0.0.0__b77a5c561934e089\System.Data.dll
Fields:
MT Field Offset Type VT Attr Value Name
000007fee4e22d50 40001cf 8 System.Object 0 instance 0000000000000000 __identity
000007fee4172658 40001ae 10 …ponentModel.ISite 0 instance 0000000000000000 site
000007fee416e0c0 40001af 18 ….EventHandlerList 0 instance 0000000000000000 events
000007fee4e22d50 40001ad 48 System.Object 0 static 0000000000000000 EventDisposed
000007fee4e28c90 4001733 b0 System.Int32 1 instance 1 ObjectID
000007fee4e234b8 4001734 20 System.String 0 instance 0000000002652f70 _commandText
000007fee1d6f378 4001735 b4 System.Int32 1 instance 0 _commandType
From the above result we can identify the _commandtext is in the 20 offset within the SqlCommand object. So to get the actual Sql text we would have do a poi(@rcx+20)+c. So what is poi? poi is pointer deference. The @rcx register has the Sqlcommand object . So essentially poi(@rcx+20)+c means get the Sql command object in the @rcx register and get a pointer of 20th offset (which has the _commandtext) and the “+c” is where the actual text is stored with in the .NET string object. The .printf is similar to printf statement in C or Console.WriteLine in .NET.
So now we will be able to dump the Sql text which is being sent to the database without opening another tool. This can even further extended by getting the parameter values and saving all sql text along with the parameter information on to a text file using .logopen command.






