C# - GC Heap에 위치한 참조 개체의 주소를 알아내는 방법
예전에 아래의 글을 설명하면서,
C#에서 확인해 보는 관리 힙의 인스턴스 구조
; https://www.sysnet.pe.kr/2/0/1176
참조 개체가 관리 힙의 어디에 위치하고 있는지 그 주솟값을 구하기 위해 fixed를 사용하려고 억지로 필드 하나를 정의하는 코드를 사용했는데요,
// x86 환경
class Program
{
    public int value = 0xff;
    static unsafe void Main(string[] args)
    {
        Program pg = new Program();
        fixed (int* p2 = &pg.value)
        {
            Console.WriteLine("OBJECT Header(SyncBlock Index): " + (*(p2 - 2)).ToString("x"));
            Console.WriteLine("MethodTable Address: " + (*(p2 - 1)).ToString("x"));
            Console.WriteLine("첫 번째 필드 값: " + (*(p2 - 0)).ToString("x"));
        }
    }
}
이후로 몇몇 글에서 __makeref를 이용해 참조 주솟값을 구하는 방법을 사용했었지만,
windbg/sos - Dictionary의 entries 배열 내용을 모두 덤프하는 방법 (do_hashtable.py)
; https://www.sysnet.pe.kr/2/0/12087
직접적으로 명시를 하지 않아 검색이 잘 되는군요. ^^ 그래서 검색의 목적을 위해, 아울러 혹시 궁금하신 분들을 위해 별도 제목으로 뽑아 이렇게 기록을 남깁니다. 방법은 간단하게 TypedReference와 __makeref를 이용해 다음과 같이 구할 수 있습니다.
// x86 or x64
using System;
using System.Threading;
namespace ConsoleApplication1
{
    class Program
    {
        public int value = 0xff;
        static unsafe void Main(string[] args)
        {
            Program pg = new Program();
            IntPtr ptr = GetRefAddress(pg);
        }
        private unsafe static IntPtr GetRefAddress(object obj)
        {
            TypedReference refA = __makeref(obj);
            return **(IntPtr**)&refA;
        }
        /* 또는, Dumping the managed heap in C#
        public static nint GetAddress(T obj)
        {
            // Get the address of the reference, cast it to a pointer,
            // then dereference it to get the address of the object
            return (nint)(*(T**)&obj);
        }
        */
    }
}
이 코드를 기반으로 "
C#에서 확인해 보는 관리 힙의 인스턴스 구조" 글에 사용한 코드를 다시 작성해 보면,
static unsafe void WriteRefObjectInfo(IntPtr pAddress)
{
    IntPtr pHeader = pAddress - (IntPtr.Size * 1);
    Console.WriteLine("OBJECT Header(SyncBlock Index): " + GetWordAsText(pHeader));
    Console.WriteLine("MethodTable Address: " + GetWordAsText(pAddress));
    IntPtr pIntValue = pAddress + (IntPtr.Size * 1);
    Console.WriteLine("첫 번째 int 필드 값: " + GetWordAsText(pIntValue));
}
static unsafe string GetWordAsText(IntPtr pAddress)
{
    if (IntPtr.Size == 4)
    {
        return (*((int*)pAddress.ToPointer())).ToString("x");
    }
    else
    {
        return (*((long*)pAddress.ToPointer())).ToString("x");
    }
}
다음과 같이 값이 잘 출력되는 것을 확인할 수 있습니다.
OBJECT Header(SyncBlock Index): 0
MethodTable Address: 7ff8566f5a70
첫 번째 int 필드 값: ff
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]