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:

// 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:

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:

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:

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:

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:

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.