CoreCLR源碼探索(一) Object是什麼

来源:http://www.cnblogs.com/zkweb/archive/2017/01/03/6244934.html
-Advertisement-
Play Games

.Net程式員們每天都在和Object在打交道 如果你問一個.Net程式員什麼是Object,他可能會信誓旦旦的告訴你"Object還不簡單嗎,就是所有類型的基類" 這個答案是對的,但是不足以說明Object真正是什麼 在這篇文章我們將會通過閱讀CoreCLR的源代碼瞭解Object在記憶體中的結構和 ...


.Net程式員們每天都在和Object在打交道
如果你問一個.Net程式員什麼是Object,他可能會信誓旦旦的告訴你"Object還不簡單嗎,就是所有類型的基類"
這個答案是對的,但是不足以說明Object真正是什麼

在這篇文章我們將會通過閱讀CoreCLR的源代碼瞭解Object在記憶體中的結構和實際到記憶體中瞧瞧Object

Object在記憶體中的結構

為了便於理解後面的內容,我先用一張圖說明Object在記憶體中的結構

.Net中的Object包含了這三個部分

  • 指向頭部的指針
  • 指向類型信息的指針
  • 欄位內容

微軟有一張更全的圖(說明的是.Net Framework的結構,但是基本和.Net Core一樣)

Object的源代碼解析

Object的定義(摘要)
源代碼: https://github.com/dotnet/coreclr/blob/master/src/vm/object.h

class Object
{
    PTR_MethodTable m_pMethTab;
}

PTR_MethodTable的定義,DPTR是一個指針的包裝類,你可以先理解為MethodTable*的等價
源代碼: https://github.com/dotnet/coreclr/blob/master/src/vm/common.h

typedef DPTR(class MethodTable) PTR_MethodTable;

在Object的定義中我們只看到了一個成員,這個成員就是指向類型信息的指針,那其他兩個部分呢?

這是獲取指向頭部的指針的函數,我們可以看到這個指針剛好放在了Object的前面

PTR_ObjHeader GetHeader()
{
    LIMITED_METHOD_DAC_CONTRACT;
    return dac_cast<PTR_ObjHeader>(this) - 1;
}

這是獲取欄位內容的函數,我們可以看到欄位內容剛好放在了Object的後面

PTR_BYTE GetData(void)
{
    LIMITED_METHOD_CONTRACT;
    SUPPORTS_DAC;
    return dac_cast<PTR_BYTE>(this) + sizeof(Object);
}

我們可以看到Object中雖然只定義了指向類型信息的指針,但運行時候前面會帶指向頭部的指針,並且後面會帶欄位內容
Object在記憶體中擁有不定的長度,並且起始地址是分配到的記憶體地址+一個指針的大小
Object結構比較特殊,所以這個對象的生成也需要特殊的處理,關於Object的生成我將在後面的篇幅中介紹

Object中定義的m_pMethTab還保存了額外的信息,因為這是一個指針值,所以總會以4或者8對齊,這樣最後兩個bit會總是為0
.Net利用了這兩個閑置的bit,分別用於保存GC Pinned和GC Marking,關於這裡我也將在後面的篇幅中介紹

ObjHeader的源代碼解析

ObjHeader的定義(摘要)
源代碼: https://github.com/dotnet/coreclr/blob/master/src/vm/syncblk.h

class ObjHeader
{
// !!! Notice: m_SyncBlockValue *MUST* be the last field in ObjHeader.
#ifdef _WIN64
    DWORD    m_alignpad;
#endif // _WIN64
    
    Volatile<DWORD> m_SyncBlockValue;      // the Index and the Bits
}

m_alignpad是用於對齊的(讓m_SyncBlockValue在後面4位),值應該為0
m_SyncBlockValue的前6位是標記,後面26位是對應的SyncBlockSyncBlockCache中的索引
SyncBlock的作用簡單的來說就是用於線程同步的,例如下麵的代碼會用到SyncBlock

var obj = new object();
lock (obj) { }

ObjHeader只包含了SyncBlock,所以你可以看到有的講解Object結構的文章中會用SyncBlock代替ObjHeader
關於SyncBlock更具體的講解還可以查看這篇文章

MethodTable的源代碼解析

MethodTable的定義(摘要)
源代碼: https://github.com/dotnet/coreclr/blob/master/src/vm/methodtable.h

class MethodTable
{
    // Low WORD is component size for array and string types (HasComponentSize() returns true).
    // Used for flags otherwise.
    DWORD m_dwFlags;
    
    // Base size of instance of this class when allocated on the heap
    DWORD m_BaseSize;
    
    WORD m_wFlags2;
    
    // Class token if it fits into 16-bits. If this is (WORD)-1, the class token is stored in the TokenOverflow optional member.
    WORD m_wToken;
    
    // <NICE> In the normal cases we shouldn't need a full word for each of these </NICE>
    WORD m_wNumVirtuals;
    WORD m_wNumInterfaces;
    
#ifdef _DEBUG
    LPCUTF8 debug_m_szClassName;
#endif //_DEBUG
    
    // Parent PTR_MethodTable if enum_flag_HasIndirectParent is not set. Pointer to indirection cell
    // if enum_flag_enum_flag_HasIndirectParent is set. The indirection is offset by offsetof(MethodTable, m_pParentMethodTable).
    // It allows casting helpers to go through parent chain natually. Casting helper do not need need the explicit check
    // for enum_flag_HasIndirectParentMethodTable.
    TADDR m_pParentMethodTable;
    
    PTR_Module m_pLoaderModule;    // LoaderModule. It is equal to the ZapModule in ngened images
    
    PTR_MethodTableWriteableData m_pWriteableData;
    
    union {
        EEClass *   m_pEEClass;
        TADDR       m_pCanonMT;
    };
    
    // m_pPerInstInfo and m_pInterfaceMap have to be at fixed offsets because of performance sensitive 
    // JITed code and JIT helpers. However, they are frequently not present. The space is used by other
    // multipurpose slots on first come first served basis if the fixed ones are not present. The other 
    // multipurpose are DispatchMapSlot, NonVirtualSlots, ModuleOverride (see enum_flag_MultipurposeSlotsMask).
    // The multipurpose slots that do not fit are stored after vtable slots.
    union
    {
        PTR_Dictionary *    m_pPerInstInfo;
        TADDR               m_ElementTypeHnd;
        TADDR               m_pMultipurposeSlot1;
    };
    
    union
    {
        InterfaceInfo_t *   m_pInterfaceMap;
        TADDR               m_pMultipurposeSlot2;
    };
    
    // 接下來還有一堆OPTIONAL_MEMBERS,這裡省去介紹
}

這裡的欄位非常多,我將會在後面的篇幅一一講解,這裡先說明MethodTable中大概有什麼信息

  • 類型的標記,例如StaticsMask_DynamicStaticsMask_Generics等 (m_dwFlags)
    • 如果類型是字元串或數組還會保存每個元素的大小(ComponentSize),例如string是2 int[100]是4
  • 類型需要分配的記憶體大小 (m_BaseSize)
  • 類型信息,例如有哪些成員和是否介面等等 (m_pCanonMT)

可以看出這個類型就是用於保存類型信息的,反射和動態Cast都需要依賴它

實際查看記憶體中的Object

對Object的初步分析完了,可分析對了嗎?讓我們來實際檢查一下記憶體中Object是什麼樣子的
VisualStudio有反編譯和查看記憶體的功能,如下圖

這裡我定義了MyClassMyStruct類型,先看Console.WriteLine(myClass)
這裡把第一個參數設置到rcx並且調用Console.WriteLine函數,為什麼是rcx請看查看參考鏈接中對fastcall的介紹
rbp + 0x50 = 0x1fc8fde110

跳到記憶體中以後可以看到選中的這8byte是指向對象的指針,讓我們繼續跳到0x1fcad88390

這裡我們可以看到MyClass實例的真面目了,選中的8byte是指向MethodTable的指針
後面分別是指向StringMember的指針和IntMember的內容
在這裡指向ObjHeader的指針是一個空指針,這是正常的,微軟在代碼中有註釋This is often zero

這裡是StringMember指向的內容,分別是指向MethodTable的指針,字元串長度和字元串內容

這裡是MyClassMethodTablem_BaseSize是32
有興趣的可以去和MethodTable的成員一一對照,這裡我就不跟下去了
讓我們再看下struct是怎麼處理的

可以看到只是簡單的把值複製到了堆棧空間中(rbp是當前frame的堆棧基礎地址)
讓我們再來看下Console.WriteLine對於struct是怎麼處理的,這裡的處理相當有趣

因為需要裝箱,首先會要來一個箱子,箱子放在了rbp+30h

MyStruct中的值複製到了箱子中,rax+8的8是把值複製到MethodTable之後

複製後,接下來把這個箱子傳給Console.WriteLine就和MyClass一樣了

另外再附一張實際查看ComponentSize的圖

彩蛋

看完了.Net中對Object的定義,讓我們再看下Python中隊Object的定義
源代碼: https://github.com/python/cpython/blob/master/Include/object.h

#define PyObject_HEAD PyObject ob_base; // 每個子類都需要把這個放在最開頭

typedef struct _object {
#ifdef Py_TRACE_REFS
    struct _object *_ob_next; // Heap中的前一個對象
    struct _object *_ob_prev; // Heap中的後一個對象
#endif
    Py_ssize_t ob_refcnt; // 引用計數
    struct _typeobject *ob_type; // 指向類型信息
} PyObject;

定義不一樣,但是作用還是類似的

參考

http://stackoverflow.com/questions/20033353/clr-implementation-of-virtual-method-calls-via-pointer-to-base-class
http://stackoverflow.com/questions/9808982/clr-implementation-of-virtual-method-calls-to-interface-members
http://stackoverflow.com/questions/1589669/overhead-of-a-net-array
https://en.wikipedia.org/wiki/X86_calling_conventions
https://github.com/dotnet/coreclr/blob/master/src/vm/object.inl
https://github.com/dotnet/coreclr/blob/master/src/vm/object.h
https://github.com/dotnet/coreclr/blob/master/src/vm/object.cpp
https://github.com/dotnet/coreclr/blob/master/src/vm/syncblk.h
https://github.com/dotnet/coreclr/blob/master/src/vm/syncblk.cpp
https://github.com/dotnet/coreclr/blob/master/src/vm/methodtable.inl
https://github.com/dotnet/coreclr/blob/master/src/vm/methodtable.h
https://github.com/dotnet/coreclr/blob/master/src/vm/methodtable.cpp
https://github.com/dotnet/coreclr/blob/master/src/vm/class.h
https://github.com/dotnet/coreclr/blob/master/src/inc/daccess.h
https://github.com/dotnet/coreclr/blob/master/src/debug/daccess/dacfn.cpp

寫在最後

因為是剛開始閱讀coreclr的代碼,如果有誤請在留言中指出
接下來有時間我將會著重閱讀和介紹這些內容

  • Object的生成和銷毀
  • Object繼承的原理(MethodTable)
  • Object同步的原理(ObjHeader, SyncBlock)
  • GC的工作方式
  • DACCESS

敬請期待



您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • SOLUTION VERIFIED April 27 2013 KB26727 Environment Red Hat Enterprise Linux 5 Red Hat Enterprise Linux 6 Issue How to create a bridge using a tagged ...
  • not on top of a bond Environment Red Hat Enterprise Linux 7 NetworkManager Issue Need an 802.1q VLAN in RHEL 7 when using NetworkManager. Resolution R ...
  • 我新裝的centos7主機無法使用裡面自帶的網卡,查詢後發現網卡型號為BCM4312。我在看資料安裝的過程中遇到了些問題,糾結了好久,現在分享下要註意的點,為後來的遇到同樣問題的人提供點幫助。現在開始說正事: 若要安裝以 Broadcom BCM4311、BCM4312、BCM4313、BCM432 ...
  • environment Red Hat Enterprise Linux 5.4 or later Red Hat Enterprise Linux 6.0 or later KVM virtual machines question How do I configure a bridged net ...
  • 修改環境變數PATH 最近為root添加一個環境變數發現sudo su進去沒有變化所以總結了一下所有設置環境變數的方法: 查看PATH: 1. 直接在命令行修改,就可以使用,但是只有在當前的視窗和用戶有用, 關閉以後就失效了,所以如果是臨時使用可以這樣設置 2. 修改家目錄下.bashrc文件,只對 ...
  • linux用戶分類 超級用戶:UID=0,root 普通用戶:UID 500起,由超級用戶或具有超級用戶許可權的用戶創建的用戶。 虛擬用戶:UID 1—499,為了滿足文件或服務啟動的需要而存在,一般都不能登錄,只是傀儡用戶。 用戶關聯的四個文件: /etc/passwd:用戶的賬號文件 /etc/s... ...
  • 準備工作: 實現lnmp環境 給php添加模塊,so庫 下載擴展包:memcache-2.2.5.tgz wget http://pecl.php.net/get/memcache-2.2.5.tgz ...
  • 最近在寫一個批量巡檢工具,利用ansible將腳本推到各個機器上執行,然後將執行的結果以json格式返回來。 如下所示: # ansible node2 -m script -a /root/python/health_check.py 然後將結果重定向到一個文本文件中,再通過另外一個腳本,對該文本 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...