This is the seventh part of the Custom memory allocation series. For your convenience you can find other parts in the table of contents in Part 1 — Allocating object on a stack
We know how to allocate object on a stack, however, we did this in .NET Framework. Can we do the same in .NET Core? Let’s see. I am using .NET Core 2.1.103 x64 on W10 Enterprise x64.
Code
Since I am using an 64-bit SDK, I need to change the code. Below is the modified version:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
// This application stores managed object on stack // Windbg internals: /* Load SOS: .loadby sos coreclr Select proper thread: ~0s Present all values: !clrstack -i -a Tested with .NET Core 2.1.103 x64 on W10 Enterprise x64 */ using System; using System.Threading; using System.Threading.Tasks; namespace Makeref_core { class Poco { public int Field; public void WriteField() { Console.WriteLine(Field); } public override string ToString() { return Field.ToString(); } ~Poco() { Console.WriteLine("Cleaning: " + Field); } } class Program { static void Main() { #region Copy Poco heapPoco = new Poco { Field = 5 }; Poco originalPoco = heapPoco; unsafe { var space = stackalloc ulong[10]; for (int i = 0; i < 10; ++i) { space[i] = 0xBADF00D; } TypedReference typedReference = __makeref(heapPoco); ulong* pocoAddress = (ulong*)(*(ulong*)*(ulong*)&typedReference - 8); Console.WriteLine($"Got address of an EE method: {((ulong)(pocoAddress + 1)).ToString("X")} Value: {(*(pocoAddress + 1)).ToString("X")}"); Console.ReadLine(); for (int i = 0; i < 3; ++i) { Console.WriteLine($"Address {((ulong)pocoAddress).ToString("X")} Value {(*pocoAddress).ToString("X")}"); space[i] = *pocoAddress; pocoAddress = pocoAddress + 1; } Console.WriteLine("Rewritten values"); Console.ReadLine(); *(ulong*)*(ulong*)&typedReference = (ulong)&space[1]; Console.WriteLine($"Set reference to the stack: {((ulong)&space[1]).ToString("X")}"); Console.ReadLine(); Poco stackPoco = __refvalue(typedReference, Poco); //Poco stackPoco = heapPoco; - This is the same #endregion #region Write Console.WriteLine("Got poco on stack. Displaying values:"); Console.Write("Heap: "); originalPoco.WriteField(); Console.Write("Stack: "); stackPoco.WriteField(); Console.WriteLine(); Console.WriteLine("Modifying values using references. Displaying values:"); originalPoco.Field = 555; stackPoco.Field = 444; Console.Write("Heap: "); originalPoco.WriteField(); Console.Write("Stack: "); stackPoco.WriteField(); Console.WriteLine(); Console.WriteLine("Modifying values using array on a stack. Displaying values:"); space[2] = 333; Console.Write("Heap: "); originalPoco.WriteField(); Console.Write("Stack: "); stackPoco.WriteField(); Console.ReadLine(); Console.WriteLine("Calling virtual method:"); Console.WriteLine("Heap: " + originalPoco.ToString()); Console.WriteLine("Stack: " + stackPoco.ToString()); Console.ReadLine(); Console.WriteLine("Displaying object types:"); Console.WriteLine(originalPoco.GetType()); Console.WriteLine(stackPoco.GetType()); Console.ReadLine(); Console.WriteLine("Displaying GC generations:"); Console.WriteLine(GC.GetGeneration(originalPoco)); Console.WriteLine(GC.GetGeneration(stackPoco)); Console.ReadLine(); Console.WriteLine("Threading:"); Console.WriteLine($"Taking lock in base: {DateTime.Now}"); lock (stackPoco) { Console.WriteLine($"Lock in base aquired: {DateTime.Now}"); Task.Run(() => { Console.WriteLine($"Taking lock in child: {DateTime.Now}"); lock (stackPoco) { Console.WriteLine($"Lock in child aquired: {DateTime.Now}"); } Console.WriteLine($"Lock in child released: {DateTime.Now}"); }); Thread.Sleep(2000); } Console.WriteLine($"Lock in base released: {DateTime.Now}"); Console.ReadLine(); //GC.ReRegisterForFinalize(stackPoco);// - bad idea Console.WriteLine("End"); } #endregion } } } |
It is basically the same code as in Part 1, however, instead of casting pointers to int
I use ulong
type. ALso, the syncblock is now 8 bytes before the metadata.
WinDBG
Let’s see the WinDBG session. We start the application, wait for first ReadLine
and attach the debugger:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
0:003> .loadby sos coreclr 0:003> ~0s ntdll!NtReadFile+0x14: 00007fff`e6545464 c3 ret 0:000> !clrstack -a -i Loaded C:\tmp\mscordbi.dll\5A81EF0016d000\mscordbi.dll Loaded C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.0.6\mscordaccore.dll Dumping managed stack and managed variables using ICorDebug. ============================================================================= Child SP IP Call Site 000000d38d57d858 00007fffe6545464 [NativeStackFrame] 000000d38d57d8e0 00007fff3f07214f 000000d38d57d910 (null) [Managed to Unmanaged transition: 000000d38d57d910] 000000d38d57d9a0 00007fffb2e28f65 [DEFAULT] I4 System.ConsolePal+WindowsConsoleStream.ReadFileNative(I,SZArray UI1,I4,I4,Boolean,ByRef I4,Boolean) (C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.0.6\System.Console.dll) PARAMETERS: + (Error 0x80131304 retrieving parameter 'hFile') + (Error 0x80131304 retrieving parameter 'bytes') + (Error 0x80131304 retrieving parameter 'offset') + (Error 0x80131304 retrieving parameter 'count') + (Error 0x80131304 retrieving parameter 'isPipe') + (Error 0x80131304 retrieving parameter 'bytesRead') + (Error 0x80131304 retrieving parameter 'useFileAPIs') LOCALS: + (Error 0x80004005 retrieving local variable 'readSuccess') + (Error 0x80004005 retrieving local variable 'errorCode') + (Error 0x80004005 retrieving local variable 'p') + (Error 0x80004005 retrieving local variable 'local_3') + (Error 0x80004005 retrieving local variable 'charsRead') 000000d38d57da00 00007fffb2e28db3 [DEFAULT] [hasThis] I4 System.ConsolePal+WindowsConsoleStream.Read(SZArray UI1,I4,I4) (C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.0.6\System.Console.dll) PARAMETERS: + (Error 0x80131304 retrieving parameter 'this') + (Error 0x80131304 retrieving parameter 'buffer') + (Error 0x80131304 retrieving parameter 'offset') + (Error 0x80131304 retrieving parameter 'count') LOCALS: + (Error 0x80004005 retrieving local variable 'bytesRead') + (Error 0x80004005 retrieving local variable 'errCode') 000000d38d57da70 00007fffa3ccdc6d [DEFAULT] [hasThis] I4 System.IO.StreamReader.ReadBuffer() (C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.0.6\System.Runtime.Extensions.dll) PARAMETERS: + System.IO.StreamReader this @ 0x22b678659f0 LOCALS: + (Error 0x80004005 retrieving local variable 'len') + (Error 0x80004005 retrieving local variable 'local_1') 000000d38d57dac0 00007fffa3cce04a [DEFAULT] [hasThis] String System.IO.StreamReader.ReadLine() (C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.0.6\System.Runtime.Extensions.dll) PARAMETERS: + System.IO.StreamReader this @ 0x22b678659f0 LOCALS: + (Error 0x80004005 retrieving local variable 'sb') + (Error 0x80004005 retrieving local variable 'i') + (Error 0x80004005 retrieving local variable 'ch') + (Error 0x80004005 retrieving local variable 's') 000000d38d57db10 00007fffb2e2d517 [DEFAULT] [hasThis] String System.IO.SyncTextReader.ReadLine() (C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.0.6\System.Console.dll) PARAMETERS: + System.IO.SyncTextReader this @ 0x22b678663b0 LOCALS: + (Error 0x80004005 retrieving local variable 'local_0') + (Error 0x80004005 retrieving local variable 'local_1') + (Error 0x80004005 retrieving local variable 'local_2') 000000d38d57db60 00007fffb2e252fa [DEFAULT] String System.Console.ReadLine() (C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.0.6\System.Console.dll) PARAMETERS: (none) LOCALS: (none) 000000d38d57db90 00007fff3f0709b8 [DEFAULT] Void Makeref_core.Program.Main() (C:\Users\user\Desktop\msp_windowsinternals\Makeref_core\bin\Debug\netcoreapp2.0\Makeref_core.dl) PARAMETERS: (none) LOCALS: + Makeref_core.Poco heapPoco @ 0x22b67863840 + Makeref_core.Poco originalPoco @ 0x22b67863840 + Makeref_core.Program+c__DisplayClass0_0 CS$8__locals0 @ 0x22b67863858 + unsigned long* space = 195948557 + typedbyref typedReference @ 0x22b67863840 + unsigned long* pocoAddress = 0 + int i = 10 + (Error 0x80004005 retrieving local variable 'local_7') + (Error 0x80004005 retrieving local variable 'local_8') + int i = 0 + (Error 0x80004005 retrieving local variable 'local_10') + (Error 0x80004005 retrieving local variable 'local_11') + (Error 0x80004005 retrieving local variable 'local_12') 000000d38d57de00 00007fff9eb735f3 [NativeStackFrame] Stack walk complete. ============================================================================= |
Nothing special here, let’s examine the stack:
1 2 3 4 5 6 7 8 9 |
0:000> dd 000000d38d57db90 000000d3`8d57db90 67865760 0000022b 00000000 00000000 000000d3`8d57dba0 6786575c 0000022b 8d57d970 000000d3 000000d3`8d57dbb0 0badf00d 00000000 0badf00d 00000000 000000d3`8d57dbc0 0badf00d 00000000 0badf00d 00000000 000000d3`8d57dbd0 0badf00d 00000000 0badf00d 00000000 000000d3`8d57dbe0 0badf00d 00000000 0badf00d 00000000 000000d3`8d57dbf0 0badf00d 00000000 0badf00d 00000000 000000d3`8d57dc00 8d57dbe0 000000d3 9ea4eeeb 00007fff |
We can see the 0badf00d
markers. Let’s continue the application, and after one step check the stack again:
1 2 3 4 5 6 7 8 9 |
0:003> dd 000000d38d57db90 000000d3`8d57db90 67865760 0000022b 00000000 00000000 000000d3`8d57dba0 6786575c 0000022b 8d57d970 000000d3 000000d3`8d57dbb0 00000000 00000000 3ef15e20 00007fff 000000d3`8d57dbc0 00000005 00000000 0badf00d 00000000 000000d3`8d57dbd0 0badf00d 00000000 0badf00d 00000000 000000d3`8d57dbe0 0badf00d 00000000 0badf00d 00000000 000000d3`8d57dbf0 0badf00d 00000000 0badf00d 00000000 000000d3`8d57dc00 8d57dbe0 000000d3 9ea4eeeb 00007fff |
Yes, this looks correct. Let’s verify with the object metadata directly:
1 2 3 4 5 6 7 8 9 |
0:003> !do 0x22b67863840 Name: Makeref_core.Poco MethodTable: 00007fff3ef15e20 EEClass: 00007fff3f062110 Size: 24(0x18) bytes File: C:\Users\user\Desktop\msp_windowsinternals\Makeref_core\bin\Debug\netcoreapp2.0\Makeref_core.dll Fields: MT Field Offset Type VT Attr Value Name 00007fff978b6648 4000001 8 System.Int32 1 instance 5 Field |
The MethodTable part is correct so the whole object is copied to the stack correctly. We can verify this differently:
1 2 3 4 5 6 7 8 9 |
0:003> !do 000000d3`8d57dbb8 Name: Makeref_core.Poco MethodTable: 00007fff3ef15e20 EEClass: 00007fff3f062110 Size: 24(0x18) bytes File: C:\Users\user\Desktop\msp_windowsinternals\Makeref_core\bin\Debug\netcoreapp2.0\Makeref_core.dll Fields: MT Field Offset Type VT Attr Value Name 00007fff978b6648 4000001 8 System.Int32 1 instance 5 Field |
So we dump the object directly from the stack and it works.
Summary
There was no TypedReference
in .NET Core for a long time. Right now it is available so we can toy with memory and check what has been changed.