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

  1. AppName
  2. Version
  3. ?
  4. Assembly and Module Name
  5. Assembly Version
  6. ?
  7. MethodDef
  8. IL Offset
  9. Exception Type
  10. ?

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.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: