-
Notifications
You must be signed in to change notification settings - Fork 0
Exploring Locking and Hash codes with WinDBG and SOS
Today we're going to find out how Object Header holds auxillary information about a managed object's instance including:
- synchronization flags
- hash code when
GetHasCode()method is not overridden
We are going to use debug a simple C# application with WinDBG + SOS debugger extension
using System;
namespace TestProfiler
{
class SyncClass
{
}
class Program
{
static void Main(string[] args)
{
var x = new SyncClass();
// Console.WriteLine(x.GetHashCode());
lock (x)
{
Console.WriteLine("Locked");
// var p = x.GetHashCode();
// Console.WriteLine(p);
}
Console.WriteLine("Unlocked");
}
}
}Let's get started.
MT - Method Table Pointer
OH - Object Header
Every reference type in .net has the following layout on the managed heap
Object Reference aka Type Handle is a pointer to the MT.
Before MT, with negative 4 bytes offset, there's a data structure called Object Header.
Let’s start with setting a breakpoint inside the Main method on line 15, right after an instance of SyncClass has been created.
0:000> !bpmd Program.cs:15
0:000> gTo find more about the created instance, we dump the heap and trying to locate the address of the instance
0:000> !DumpHeap -type SyncClass
Address MT Size
02f62440 012c4dc8 12
Total 1 objectsThe output shows that a single instance of SyncClass exists in the heap.
Address column is a pointer to the type's MT. Having found out the address of the MT, we can dump both OH and MT with the following command
0:000> dd 02f62440 - 4 L2
00000000 012c4dc8We subtracted 4 from the
MT's address becauseOHhas a -4 bytes offset relative toMTaddress
- the first 8 bytes represent the
OH - the second 8 bytes are a pointer to the
MT
As we see, the OH is zeroed which means that neither the object was for synchronization nor the GetHashCode() method was called.
Now we can proceed and try using the object for synchronization, so we’re setting a breakpoint on line 21 inside the lock statement
0:000> !bpmd Program.cs:21
0:000> gand dump OH together with MT again
0:000> dd 02f62440 - 4 L2
00000001 012c4dc8Now the OH has a bit set to 1 indicating that the object is being used for synchronization.
When the latter happens, the CLR can either creates a thin lock or a sync block. Thin lock is an optimization and it’s used when, for example, there’s no other thread accessing the locked resource.
We can check existing sync blocks by the command
0:000> !SyncBlk
Total 0which shows that no sync blocks were created.
Now let's check for thin locks
0:000> !DumpHeap -thinlock
Address MT Size
02fe2440 012c4dc8 12 ThinLock owner 1 (010fa288) Recursive 0
Found 1 objects.As we see, a thin lock was created by the CLR.
Next, we leave the lock statement and examine OH once again
0:000> !bpmd Program.cs:23
0:000> g
0:000> dd 02f62440 - 4 L2
00000000 012c4dc8ANow the OH is zeroed again which means the object is no longer used for synchronization.
Let’s modify the program and call the GetHashCode() method twice: outside and inside the lock statement.
using System;
namespace TestProfiler
{
class SyncClass
{
}
class Program
{
static void Main(string[] args)
{
var x = new SyncClass();
Console.WriteLine(x.GetHashCode());
lock (x)
{
Console.WriteLine("Locked");
var p = x.GetHashCode();
Console.WriteLine(p);
}
Console.WriteLine("Unlocked");
}
}
}We set a breakpoint after GetHashCode() method was called for the first time.
0:000> !bpmd Program.cs:16
0:000> gIn the console, we see that the object's hash code is equal to 46104728. We'll need the binary and hex representation of this value so let's convert it
.formats 0n46104728
Evaluate expression:
Hex: 02bf8098
Binary: 00000010 10111111 10000000 10011000Repeat the same steps we did in part 1 to dump the object's OH and MT.
0:000> !DumpHeap -type SyncClass
Address MT Size
02852440 00a04dc8 12
0:000> dd 02852440 - 4 L2
0ebf8098 00a04dc8The first 8 bytes aren't zero and have a value, let's find out what it is but first convert the value into binary representation
.formats 0ebf8098
Evaluate expression:
Hex: 0ebf8098
Binary: 00001110 10111111 10000000 10011000Let's compare binary representations of the value we see in the console and the value we dumped from the OH
000000 10 10111111 10000000 10011000 - Console
000011 10 10111111 10000000 10011000 - Header
We can clearly see that the hash code presents in the OH
It's time to move on to the most interesting part. What happens when we call the GetHashCode() method inside the lock statement?
Let's set a breakpoint on line 19 inside the lock and dump OH and MT again
0:000> !bpmd Program.cs:19
0:000> g
0:000> dd 02852440-4 L2
08000001 00a04dc8
0:000> .formats 08000001
Evaluate expression:
Binary: 00001000 00000000 00000000 00000001Interesting, the hash code is gone and new flags have been set. As we already remember, last bit set to 1 indicates that the object is being used for synchronization. Keeping in mind the two types of synchronization mechanisms (thin locks and sync blocks) we can find out what the other bits mean. Let's check for a thin lock first
0:000> !DumpHeap -thinlock
Address MT Size
Found 0 objects.Nothing was found, so maybe there's a sync block?
0:000> !SyncBlk
Index SyncBlock MonitorHeld Recursion Owning Thread Info SyncBlock Owner
1 00a8e9bc 1 1 00a6a2b8 2688 0 02852440 TestProfiler.SyncClass
-----------------------------
Total 1
Free 0A sync block has been created. But why? The only difference with the first example is that we called GetHashCode() method prior to acquiring the lock.
Now let's call the GetHashCode() inside the lock statement and examine the OH
0:000> !bpmd Program.cs:21
0:000> g
0:000> dd 02852440-4 L2
08000001 00a04dc8The OH is intact, hash code value is displayed in the console though. So where is it stored?
Maybe the value was moved to the sync block? Let's check it by dumping the sync block's memory.
0:000> dd 00a8e9bc
00a8e9bc 00000001 00000001 00a6a2b8 00000000
00a8e9cc 80000001 ffffffff 00000000 00000000
00a8e9dc 00000000 00000000 00000000 02bf8098The last 4 bytes hold the object's hashcode values (remember the hex value we converted earlier?). This means that when a sync block is created, the hashcode value is transferred and stored in its memory.
