Archive for the ‘Windbg’ Category
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 }
Using Managed Code to debug Memory Dumps
I happened to notice the new DebugDiag 1.2 and it had COM based API for dbgeng. The sample code were in VB Script. I much comfortable writing managed code compared to VB script. So I decided to use COM based API in managed code.
Here are couple of ways to solve certain problems using this
- Parallel GC Roots :- Getting GC Roots from memory dump is the most time consuming because SOS is single threaded. I use PFX to do them in parallel.
- Reconstructing manged objects :- Creating an instance of an object by reading data from the memory dump.
Need to add reference to the COM Library
And in VS2010 (.NET 4.0) by default COM Interop types have Embed Interop Types turned on. I couldn’t compile the code with this option. I had to turn off Embed Interop types.
Few extension methods for the DbgObj
static class DbgExtensions {
public static DbgObj OpenDump(this DbgControlClass dbg, string dumpPath) {
var path = Environment.GetEnvironmentVariable("_NT_SYMBOL_PATH");
return dbg.OpenDump(dumpPath, path, path, null);
}
public static void LoadSOS(this DbgObj dbg){
// By default it only loads psscor2.dll and It will not work for .NET 4.0
dbg.UnloadExtensions();
// Will load sos based on the framework version
var sos = dbg.GetModuleByModuleName("clr") == null ? ".loadby sos mscorwks" : ".loadby sos clr";
dbg.Execute(sos);
}
public static IEnumerable<string> DumpHeap(this DbgObj dbg, string typeorMT, bool isMT = false) {
var parameter = isMT ? "-MT " : "-type ";
return dbg.Execute("!dumpheap -short " + parameter + typeorMT).Split(new[] { "\n" },
StringSplitOptions.RemoveEmptyEntries);
}
public static string GCRoot(this DbgObj dbg, string address) {
return dbg.Execute("!GcRoot" + address);
}
public static double ReadDouble(this DbgObj dbg, string address, string offset) {
return (double)Int32.Parse(
dbg.Execute(string.Format("dd {0}+{1} L1", address, offset)).Replace("\n", "")
.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries)
.ElementAt(1),
NumberStyles.AllowHexSpecifier);
}
public static string ReadString(this DbgObj dbg, string address, string offset) {
// The managed string in x86 starts at 8th offset
return dbg.ReadUnicodeString(ReadDouble(dbg, address, offset) + 8);
}
}
Parallel GC Roots
Anybody who is debugged memory dumps for leaks understands the pain of running gcroots within a loop. AFAIK sos is single threaded. I have had customers who had 24 way CPU’s who wanted to use all the CPU’s to debug memory leaks, but it wasn’t possible.
Here is a code that would make parallel gc roots possible
using System;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
using DbgHostLib;
namespace ConsoleApplication1 {
class Program {
static void Main(string[] args) {
var dump = new DbgControlClass().OpenDump(@"C:\TestClass.dmp").LoadSOS();
var testInstances = dump.DumpHeap("Test.TestClass");
var roots = testInstances.AsParallel().Select(testclass =>
new DbgControlClass().OpenDump(@"C:\TestClass.dmp").LoadSOS().GCRoot(testclass)).ToList();
Console.Read();
}
}
static class DbgExtensions {
public static DbgObj OpenDump(this DbgControlClass dbg, string dumpPath) {
var path = Environment.GetEnvironmentVariable("_NT_SYMBOL_PATH");
return dbg.OpenDump(dumpPath, path, path, null);
}
public static DbgObj LoadSOS(this DbgObj dbg){
// By default it loads psscor2
dbg.UnloadExtensions();
// Will load sos based on the framework version
var sos = dbg.GetModuleByModuleName("clr") == null ? ".loadby sos mscorwks" : ".loadby sos clr";
dbg.Execute(sos);
return dbg;
}
public static IEnumerable<string> DumpHeap(this DbgObj dbg, string typeorMT, bool isMT = false) {
var parameter = isMT ? "-MT " : "-type ";
return dbg.Execute("!dumpheap -short " + parameter + typeorMT).Split(new[] { "\n" },
StringSplitOptions.RemoveEmptyEntries);
}
public static string GCRoot(this DbgObj dbg, string address) {
var s = dbg.IsClrExtensionMissing;
return dbg.Execute("!gcroot " + address);
}
public static double ReadDouble(this DbgObj dbg, string address, string offset) {
return (double)Int32.Parse(
dbg.Execute(string.Format("dd {0}+{1} L1", address, offset)).Replace("\n", "")
.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries)
.ElementAt(1),
NumberStyles.AllowHexSpecifier);
}
public static string ReadString(this DbgObj dbg, string address, string offset) {
// The managed string in x86 starts at 8th offset
return dbg.ReadUnicodeString(ReadDouble(dbg, address, offset) + 8);
}
}
}
The above code loads a memory dump and looks for object type “Test.TestClass” and gets its addresses. Then gets GCRoots in parallel using the AsParallel option.
Reconstructing manged objects
Using the same API it is pretty easy to create an actual instance of a class from a memory dump. Here is the code for which I dumped the memory.
using System;
namespace Test {
class Program {
static Foo[] foo= new Foo[5];
static void Main(string[] args) {
for (int i = 0; i < 5; i++)
foo[i] = new Foo() { counter = i, Name = "Name " + i.ToString() };
Console.WriteLine(foo);
Console.Read();
}
}
class Foo {
public int counter;
public string Name;
public override string ToString() {
return string.Format("Counter :- {0} , Name :- {1} ", counter, Name);
}
}
}
Here is the memory structure of Foo
0:005> !do 00f1c660
Name: Test.Foo
MethodTable: 009b38bc
EEClass: 009b14a4
Size: 16(0×10) bytes
File: C:\Foo\bin\Debug\Foo.exe
Fields:
MT Field Offset Type VT Attr Value Name
79ba2978 4000002 8 System.Int32 1 instance 2 counter
79b9f9ac 4000003 4 System.String 0 instance 00f1c680 Name
Notice the variable “Name” is in the 4th offset and counter is in the 8th offset. I use these offsets to read its contents from the dump.Here is the code that recreates instances of Foo from the memory dump.
class Program {
static void Main(string[] args) {
var dump = new DbgControlClass().OpenDump(@"C:\temp\Foo.dmp").LoadSOS();
var foos = dump.DumpHeap(@"Test.Foo");
foos.Select(s => new Foo() { counter = (int)dump.ReadDouble(s, "0x8"), Name = dump.ReadString(s, "0x4") }).
ToList().ForEach(Console.WriteLine);
Console.Read();
}
}
And here is the output from the above code.
Counter :- 0 , Name :- Name 0
Counter :- 1 , Name :- Name 1
Counter :- 2 , Name :- Name 2
Counter :- 3 , Name :- Name 3
Counter :- 4 , Name :- Name 4
There is lot more to explore than what I have shown above. Happy debugging :)
Dumping .NET strings to files using Windbg
In this post I would demonstrate how to dump strings from a memory dump /live process to a file. Recently I had to debug a process which had few big strings where I had to analyze its contents. The !dumpobj from sos would only dump partial strings. I had to dump few hundred XML strings that I had to analyze using some automation. And hence comes the script.
$$ Dumps the managed strings to a file
$$ Platform x86
$$ Naveen Srinivasan http://naveensrinivasan.com
$$ Usage $$>a<"c:\temp\dumpstringtofolder.txt" 6544f9ac 5000 c:\temp\stringtest
$$ First argument is the string method table pointer
$$ Second argument is the Min size of the string that needs to be used filter
$$ the strings
$$ Third is the path of the file
.foreach ($string {!dumpheap -short -mt ${$arg1} -min ${$arg2}})
{
$$ MT Field Offset Type VT Attr Value Name
$$ 65452978 40000ed 4 System.Int32 1 instance 71117 m_stringLength
$$ 65451dc8 40000ee 8 System.Char 1 instance 3c m_firstChar
$$ 6544f9ac 40000ef 8 System.String 0 shared static Empty
$$ start of string is stored in the 8th offset, which can be inferred from above
$$ Size of the string which is stored in the 4th offset
r@$t0= poi(${$string}+4)*2
.writemem ${$arg3}${$string}.txt ${$string}+8 ${$string}+8+@$t0
}
And to use the above script ,copy it to a file and invoke it within Windbg/cdb
$$>a<”c:\temp\dumpstringtofolder.txt” 6544f9ac 5000 c:\temp\stringtest
Parameters to the script
- 6544f9ac :- Is the MT to string.
- 5000 :- Is the min size of the string that I want to dump
- c:\temp\stringtest :- Is the path along with partial filename for each string item
The dumped contents would be in Unicode format and to view its contents use something like this
Console.WriteLine(ASCIIEncoding.Unicode.GetString(File.ReadAllBytes(@"c:\temp\stringtest03575270.txt")));
And here is a sample code that downloads big xml strings ,that can be used by the above script to dump its contents to a folder
using System;
using System.Net;
namespace Test
{
class Program
{
static void Main(string[] args)
{
var speakers = new WebClient().DownloadString("http://www.codemash.org/rest/speakers");
var sessions = new WebClient().DownloadString("http://www.codemash.org/rest/sessions");
Console.Read();
}
}
}
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.
GC Start and Stop events in .NET using Windbg
I was recently showing someone the new ETW features in .NET especially the GC Event notification and I was asked if we can get this using Windbg.
So here is the sample code for the GC Collection
namespace GCStartStop
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
button1.Click += (s, b) => GC.Collect(2);
button1.Click += (s, b) => GC.Collect(1);
}
}
}
The goal is set to a break-point only when the collection count is 2. Here is a bp script for doing this.
bp clr!WKS::GCHeap::SuspendEE ".if (dwo(clr!WKS::GCHeap::GcCondemnedGeneration)==2) {.echo start of gen 2;g} .else {gc}"
The same thing can be done for clr!WKS::GCHeap::RestartEE.
When showing this to someone I was asked what does “EE” acronym in “SuspendedEE” ? “EE” is Execution Engine.
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 .NET – mystery between DEBUG versus RELEASE within windbg
I am sure most of us have debugged applications that are build with debug turned on, which is obviously much easier compared to debugging release build (optimized code). In this post I am going to share one of my experiences of debugging release build code. I will demonstrate this with a simple Console Application.
Here is the code
using System;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
var x = 10;
var name = "naveen";
Console.WriteLine(name);
Console.Read();
}
}
}
I compiled it under release mode and launched it within the debugger. The goal is to have a break-point on Console.WriteLine(name). First thing was to set up a sx notification on load for mscorlib.
sxe ld:mscorlib
And when the break-point hits for the above event, then issued the following command to load sosex , sos and set an bp on Console.WriteLine which is nothing fancy
.load sosex;.loadby sos clr;!mbm *System.Console.WriteLine* "!mk";g
I would imagine that the break-point would hit and I would get a call-stack, but to my surprise this was the output
0:000> .load sosex;.loadby sos clr;!mbm *System.Console.WriteLine* “!mk”;g
The breakpoint could not be resolved immediately.
Further attempts will be made as modules are loaded.
(1090.11fc): CLR notification exception – code e0444143 (first chance)
Breakpoint set at System.Console.WriteLine().
Breakpoint set at System.Console.WriteLine(Boolean).
Breakpoint set at System.Console.WriteLine(Char).
Breakpoint set at System.Console.WriteLine(Char[]).
Breakpoint set at System.Console.WriteLine(Char[], Int32, Int32).
Breakpoint set at System.Console.WriteLine(System.Decimal).
Breakpoint set at System.Console.WriteLine(Double).
Breakpoint set at System.Console.WriteLine(Single).
Breakpoint set at System.Console.WriteLine(Int32).
Breakpoint set at System.Console.WriteLine(UInt32).
Breakpoint set at System.Console.WriteLine(Int64).
Breakpoint set at System.Console.WriteLine(UInt64).
Breakpoint set at System.Console.WriteLine(System.Object).
Breakpoint set at System.Console.WriteLine(System.String).
Breakpoint set at System.Console.WriteLine(System.String, System.Object).
Breakpoint set at System.Console.WriteLine(System.String, System.Object, System.Object).
Breakpoint set at System.Console.WriteLine(System.String, System.Object, System.Object, System.Object).
Breakpoint set at System.Console.WriteLine(System.String, System.Object, System.Object, System.Object, System.Object, …).
Breakpoint set at System.Console.WriteLine(System.String, System.Object[]).
(1090.11fc): CLR notification exception – code e0444143 (first chance)
That’s it. And I never got an hit for the break-point. I checked to make sure there was an actual breakpoint set by issuing a “bl” command. I could see there were break-points for Console.WriteLine. The next step was to disassemble the code. So got the instruction pointer from the !mk call-stack. Here is the output of !mk. FYI this is when the code is blocked on Console.Read
00:U 003def90 75d273ea KERNEL32!ReadConsoleInternal+0×15
01:U 003def98 75d27041 KERNEL32!ReadConsoleA+0×40
02:U 003df020 75caf489 KERNEL32!ReadFileImplementation+0×75
03:M 003df068 65651c8b DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)(+0×0 IL)(+0×0 Native)
04:M 003df0e8 65cbf7e8 System.IO.__ConsoleStream.ReadFileNative(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte[], Int32, Int32, Int32, Int32 ByRef)(+0×53 IL)(+0x8c Native) [f:\dd\ndp\clr\src\BCL\System\IO\__ConsoleStream.cs, @ 16707566,0]
05:M 003df110 65cbf6d0 System.IO.__ConsoleStream.Read(Byte[], Int32, Int32)(+0x5d IL)(+0x9c Native) [f:\dd\ndp\clr\src\BCL\System\IO\__ConsoleStream.cs, @ 131,13]
06:M 003df138 65608bfb System.IO.StreamReader.ReadBuffer()(+0xa0 IL)(+0x3b Native) [f:\dd\ndp\clr\src\BCL\System\IO\StreamReader.cs, @ 488,21]
07:M 003df154 65bcacc3 System.IO.StreamReader.Read()(+0x1b IL)(+0×23 Native) [f:\dd\ndp\clr\src\BCL\System\IO\StreamReader.cs, @ 302,17]
08:M 003df160 65cc5e9d System.IO.TextReader+SyncTextReader.Read()(+0×0 IL)(+0×19 Native) [f:\dd\ndp\clr\src\BCL\System\IO\TextReader.cs, @ 244,17]
09:M 003df170 0066009a *** WARNING: Unable to verify checksum for ConsoleApplication.exe
ConsoleApplication.Program.Main(System.String[])(+0×0 IL)(+0x2a Native) [c:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication11\Program.cs, @ 10,13]
0a:U 003df17c 661621db clr!CallDescrWorker+0×33
Next disassemble Main Method using the ip which is 0066009a
!u 0066009a
Here is the output
0:000> !u 0066009a
Normal JIT generated code
ConsoleApplication.Program.Main(System.String[])
Begin 00660070, size 2d
c:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication11\Program.cs @ 10:
00660070 55 push ebp
00660071 8bec mov ebp,esp
00660073 56 push esi
00660074 8b3530206b03 mov esi,dword ptr ds:[36B2030h] (“naveen”)
c:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication11\Program.cs @ 11:
0066007a e85170f864 call mscorlib_ni+0x2570d0 (655e70d0) (System.Console.get_Out(), mdToken: 060008cd)
0066007f 8bc8 mov ecx,eax
00660081 8bd6 mov edx,esi
00660083 8b01 mov eax,dword ptr [ecx]
00660085 8b403c mov eax,dword ptr [eax+3Ch]
00660088 ff5010 call dword ptr [eax+10h]
0066008b e8f0a55565 call mscorlib_ni+0x82a680 (65bba680) (System.Console.get_In(), mdToken: 060008cc)
00660090 8bc8 mov ecx,eax
00660092 8b01 mov eax,dword ptr [ecx]
00660094 8b402c mov eax,dword ptr [eax+2Ch]
00660097 ff500c call dword ptr [eax+0Ch]
c:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication11\Program.cs @ 13:
>>> 0066009a 5e pop esi
0066009b 5d pop ebp
0066009c c3 ret
And I see System.Console.get_Out instead of System.Console.WriteLine which I was totally surprised. This was the reason the break-point never hit. Next I wanted check the IL which was compiled , what we see above is jitted x86 mixed with IL. Here is the command to check the compiled IL. First I had to get the methodesc from the ip using !ip2md
!ip2md 0066009a
0:000> !ip2md 0066009a
MethodDesc: 002237f0
Method Name: ConsoleApplication.Program.Main(System.String[])
Class: 002213f8
MethodTable: 00223804
mdToken: 06000001
Module: 00222e9c
IsJitted: yes
CodeAddr: 00660070
Transparency: Critical
Source file: c:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication11\Program.cs @ 13
Here is from the methoddesc to IL
!dumpil 002237f0
0:000> !dumpil 002237f0
ilAddr = 012a2050
IL_0000: ldstr “naveen”
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: call System.Console::WriteLine
IL_000c: call System.Console::Read
IL_0011: pop
IL_0012: ret
Which looks very similar to my C# code. So looks like CLR optimized the code ,converted the Console.WriteLine to Console.get_Out. To validate it restarted the app with this as the command for break-point
.load sosex;.loadby sos clr;!mbm *System.Console.get_Out* "!mk";g
And here is the output
00:M 0032ed58 655e70d1 System.Console.get_Out()(+0×0 IL)(+0×1 Native) [f:\dd\ndp\clr\src\BCL\System\Console.cs, @ 193,17]
01:M 0032ed60 003a007f *** WARNING: Unable to verify checksum for ConsoleApplication.exe
ConsoleApplication.Program.Main(System.String[])(+0×6 IL)(+0xf Native) [c:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication11\Program.cs, @ 11,13]
02:U 0032ed6c 661621db clr!CallDescrWorker+0×33
Now that I have solved this I wanted to check the same on the debug build (optimized -) . To validate if it was Console.get_Out or Console.WriteLine. So when mscorlib loaded here was my command to check this
.load sosex;.loadby sos clr;!mbm *Program.Main* "!u @eip";g
In the above command I am setting a break-point on Main method and when the break-point hits “!u @eip” will disassemble the ip, the @eip register will have the address of the current function. Here is the output from !u @eip
*** WARNING: Unable to verify checksum for ConsoleApplication11.exe
c:\users\naveen\documents\visual studio 2010\Projects\ConsoleApplication11\Program.cs @ 11:
01f10070 55 push ebp
01f10071 8bec mov ebp,esp
01f10073 83ec0c sub esp,0Ch
01f10076 894dfc mov dword ptr [ebp-4],ecx
01f10079 833d3c31360000 cmp dword ptr ds:[36313Ch],0
01f10080 7405 je 01f10087
01f10082 e8c85a5064 call clr!JIT_DbgIsJustMyCode (66415b4f)
01f10087 33d2 xor edx,edx
01f10089 8955f4 mov dword ptr [ebp-0Ch],edx
01f1008c 33d2 xor edx,edx
01f1008e 8955f8 mov dword ptr [ebp-8],edx
>>> 01f10091 90 nop
c:\users\naveen\documents\visual studio 2010\Projects\ConsoleApplication11\Program.cs @ 12:
01f10092 c745f80a000000 mov dword ptr [ebp-8],0Ah
c:\users\naveen\documents\visual studio 2010\Projects\ConsoleApplication11\Program.cs @ 13:
01f10099 8b0530200f03 mov eax,dword ptr ds:[30F2030h] (“naveen”)
01f1009f 8945f4 mov dword ptr [ebp-0Ch],eax
c:\users\naveen\documents\visual studio 2010\Projects\ConsoleApplication11\Program.cs @ 14:
01f100a2 8b4df4 mov ecx,dword ptr [ebp-0Ch]
*** WARNING: Unable to verify checksum for C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\246f1a5abb686b9dcdf22d3505b08cea\mscorlib.ni.dll
01f100a5 e802706d63 call mscorlib_ni+0x2570ac (655e70ac) (System.Console.WriteLine(System.String) , mdToken: 06000919)
01f100aa 90 nop
c:\users\naveen\documents\visual studio 2010\Projects\ConsoleApplication11\Program.cs @ 15:
01f100ab e8b4c1ca63 call mscorlib_ni+0x82c264 (65bbc264) (System.Console.Read(), mdToken: 0600090a)
01f100b0 90 nop
c:\users\naveen\documents\visual studio 2010\Projects\ConsoleApplication11\Program.cs @ 17:
01f100b1 90 nop
01f100b2 8be5 mov esp,ebp
01f100b4 5d pop ebp
01f100b5 c3 ret
eax=003637f0 ebx=00000000 ecx=020fbc7c edx=00000000 esi=004bb2d0 edi=0016f400
eip=01f10091 esp=0016f3c8 ebp=0016f3d4 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
01f10091 90 nop
Notice in the above code it is Console.WriteLine and not Console.get_Out.
Here is one of gotchas of debugging optimized code.
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.
Recursive !dumpmt – Windbg
In this post I will be demonstrating how we could use CLR internal data-structures to recursively get the methodtable’s of an object and its base classes. The idea behind this is to understand the CLR data structure.
Here is the sample code
using System;
namespace ConsoleApplication
{
class Program : B
{
string test = "cw";
static void Main(string[] args)
{
var p = new Program();
Console.Read();
}
}
class B : A
{
public void TestB()
{
}
}
class A
{
public void TestA()
{
}
}
}
The “Program” object address is 0x0254bc38 and here is the output of !dumpobj
0:000> !do 0x0254bc38
Name: ConsoleApplication.Program
MethodTable: 001c3904
EEClass: 001c1508
Size: 12(0xc) bytes
File: C:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication9\bin\Debug\ConsoleApplication.exe
Fields:
MT Field Offset Type VT Attr Value Name
6335f9ac 4000001 4 System.String 0 instance 0254bc44 test
The method table pointer for the Program class is 001c3904 . Let’s dump the raw memory instead of using !dumpobj
dd 0x0254bc38
0:000> dd 0x0254bc38
0254bc38 001c3904 0254bc44 80000000 6335f9ac
0254bc48 00000002 00770063 00000000 00000000
0254bc58 63367490 00000000 00000000 00000000
0254bc68 00000000 00000000 00000000 6335f5e8
0254bc78 00000000 40010000 63366034 00000003
0254bc88 00000008 00000100 00000000 63366f40
0254bc98 00000000 00000000 00000000 00000000
0254bca8 00000001 0254bc80 00000001 00000000
Now that we can see the MethodTable pointer is the first field we can get the Methods by using
!dumpmt -md poi(0x0254bc38)
0:000> !dumpmt -md poi(0x0254bc38)
EEClass: 001c1508
Module: 001c2e9c
Name: ConsoleApplication.Program
mdToken: 02000004
File: C:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication9\bin\Debug\ConsoleApplication.exe
BaseSize: 0xc
ComponentSize: 0×0
Slots in VTable: 6
Number of IFaces in IFaceMap: 0
————————————–
MethodDesc Table
Entry MethodDesc JIT Name
6326a7e0 63044934 PreJIT System.Object.ToString()
6326e2e0 6304493c PreJIT System.Object.Equals(System.Object)
6326e1f0 6304495c PreJIT System.Object.GetHashCode()
632f1600 63044970 PreJIT System.Object.Finalize()
002200d0 001c38f0 JIT ConsoleApplication.Program..ctor()
00220070 001c38e4 JIT ConsoleApplication.Program.Main(System.String[])
So next time when we have an object we don’t have to go look for method table pointer address.
The goal is to get every method of class Program, B, A and System.Object automatically. To get this, lets dump the raw memory of the method table
dd poi(0x0254bc38)
0:000> dd poi(0x0254bc38)
001c3904 00080000 0000000c 00050011 00000004
001c3914 001c3890 001c2e9c 001c3934 001c1508
001c3924 00000000 00000000 001c3854 002200d0
001c3934 00000080 00000000 00000000 00000000
001c3944 00000000 00000000 00000000 00000000
001c3954 00000000 00000000 00000000 00000000
001c3964 00000000 00000000 00000000 00000000
001c3974 00000000 00000000 00000000 00000000
The 10th offset contains the address of its base class method table pointer, So in the above output it is 001c3890 .
Now that we know it is the 10th offset, here is the script to get every method table for a class and its parents. FYI if the 10th offset is 00000000 then it means it is the super class which is System.Object.
r$t0 =poi(0x0254bc38);.while(@$t0) {!dumpmt -md @$t0;.echo *****************;r$t0=poi(@$t0+10)}
And here is the explanation for the above script
- r$t0 =poi(0x0254bc38) – Using a pseudo register $t0 to assign the value of mt of the 0x0254bc38
- The .while loop will terminate when the value is 0. The “!dumpmt -md @$t0″ will dump the Method Table of the $t0 and the “r$t0=poi(@$t0+10)” will reset $t0 its parent object method table.
Here is the partial output from the above script with method tables from Program and B
0:000> r$t0 =poi(0x0254bc38);.while(@$t0) {!dumpmt -md @$t0;.echo *****************;r$t0=poi(@$t0+10)}
EEClass: 001c1508
Module: 001c2e9c
Name: ConsoleApplication.Program
mdToken: 02000004
File: C:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication9\bin\Debug\ConsoleApplication.exe
BaseSize: 0xc
ComponentSize: 0×0
Slots in VTable: 6
Number of IFaces in IFaceMap: 0
————————————–
MethodDesc Table
Entry MethodDesc JIT Name
6326a7e0 63044934 PreJIT System.Object.ToString()
6326e2e0 6304493c PreJIT System.Object.Equals(System.Object)
6326e1f0 6304495c PreJIT System.Object.GetHashCode()
632f1600 63044970 PreJIT System.Object.Finalize()
002200d0 001c38f0 JIT ConsoleApplication.Program..ctor()
00220070 001c38e4 JIT ConsoleApplication.Program.Main(System.String[])
*****************
EEClass: 001c149c
Module: 001c2e9c
Name: ConsoleApplication.B
mdToken: 02000003
File: C:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication9\bin\Debug\ConsoleApplication.exe
BaseSize: 0xc
ComponentSize: 0×0
Slots in VTable: 6
Number of IFaces in IFaceMap: 0
————————————–
MethodDesc Table
Entry MethodDesc JIT Name
6326a7e0 63044934 PreJIT System.Object.ToString()
6326e2e0 6304493c PreJIT System.Object.Equals(System.Object)
6326e1f0 6304495c PreJIT System.Object.GetHashCode()
632f1600 63044970 PreJIT System.Object.Finalize()
00220120 001c3888 JIT ConsoleApplication.B..ctor()
001cc02d 001c387c NONE ConsoleApplication.B.TestB()
*****************
dumpstring – windbg
Viewing strings inside the debugger has never been pretty, especially if you are using sos extension. Here is a sample !dumpobj on a string
0:000> !do 00000000025f2280
Name: System.String
MethodTable: 000007fef6e26960
EEClass: 000007fef69aeec8
Size: 32(0×20) bytes
String: Foo
Fields:
MT Field Offset Type VT Attr Value Name
000007fef6e2c848 40000ed 8 System.Int32 1 instance 3 m_stringLength
000007fef6e2b388 40000ee c System.Char 1 instance 46 m_firstChar
000007fef6e26960 40000ef 10 System.String 0 shared static Empty
>> Domain:Value 00000000002ae900:00000000025e1420 <<
Some of the devs like to use the du command
du 00000000025f2280+c
0:000> du 00000000025f2280+c
00000000`025f228c “Foo”
My choice is to use the .printf command and here is my alias for printing string
as !ds .printf "%mu \n", c+
0:000> !ds 00000000025f2280
Foo
I prefer .printf over du because I am not interested in looking at the memory address often especially dumping strings within a script.

