前言:
.Net7的CLR最具特色的一個地方,就是執行模型。因為它主宰了整個CLR的執行過程。
又因為其龐大的程式碼量,有的幾十萬行甚至百萬行。所以理解起來非常不容易。本篇拆分來看下,裡面一個細節Main函式(注意這裡的Main指的是託管Main,因為還有非託管main,所以特別指出下以免混淆)是如何被CLR呼叫的。
概括
Main函式的呼叫,基本上是最後才會被CLR引擎所呼叫。為啥?因為,如果.Net標準類庫,以及底層的非託管C++類庫沒有在Main函式被呼叫之前,載入完成,那麼你強行呼叫Main函式就會出錯。
我們可以在Windows/Linux平臺透過Windbg或者是LLDB來看下託管的Main函式的堆疊
00007ff7f55703b0
coreclr.dll!CallDescrWorkerInternal
coreclr.dll!CallDescrWorkerWithHandler
coreclr.dll!MethodDescCallSite::CallTargetWorker
coreclr.dll!MethodDescCallSite::Call C++
coreclr.dll!RunMainInternal C++
coreclr.dll!``RunMain'::`30'
coreclr.dll!`RunMain'::`30'
coreclr.dll!RunMain C++
coreclr.dll!Assembly::ExecuteMainMethod
coreclr.dll!CorHost2::ExecuteAssembly
coreclr.dll!coreclr_execute_assembly C++
corerun.exe!run C++
corerun.exe!wmain C++
corerun.exe!invoke_main C++
corerun.exe!__scrt_common_main_seh C++
corerun.exe!__scrt_common_main C++
corerun.exe!wmainCRTStartup C++
kernel32.dll!BaseThreadInitThunk
ntdll.dll!RtlUserThreadStart
這個堆疊裡面我們可以得出很多的資訊:
比如:
1.最上面的地址:00007ff7f55703b0是.Net裡面的託管Main函式的入口地址。
2.整個程式的執行最開始的程式碼是ntdll.dll模組裡面的RtlUserThreadStart函式。
3.非託管的CLR的入口是wmain函式。
有了以上的資訊,我們很容易分析到。託管的Main函式呼叫是在程式集的ExecuteMainMethod函式里面呼叫RunMain這個函式,整個函式的名字一看就知道是執行Main函式的。
此後透過CallDescrWorkerInternal來呼叫RyuJIT編譯MSIL程式碼為機器碼。返回到託管Main函式的開頭地址。
也就是上面的這個地址:00007ff7f55703b0
分析:
這個過程看似分析非常簡單,但是實際上會遇到很多問題。但是如果你只用單一的偵錯程式,比如只用Windbg。你可能完全看不出來以下這些問題。
比如你在除錯.Ctor,也就是.Net 7裡面預設的建構函式的時候。如果在VS上Debug CLR上面,如果進入到託管Main的機器碼當中,執行到.Ctor的機器碼的地方。它會報一個不是錯誤的錯誤,而跟蹤下來居然是MapViewOfFile這個微軟的官方函式的錯誤,關於這一點可以看這裡,之前寫的一篇文章:點選這裡檢視之前文章
而另外如果你在Linux上面用LLDB除錯的時候,會發現託管的Main入口斷點可能會進不去的情況。
當然這些類似Bug但可能不是Bug東西,需要深厚的功力去發現和驗證它到底是啥。
結尾:
作者:江湖評談
歡迎關注我,帶你瞭解.Net7的CLR的各種奇巧淫技。讓.Net7,甚至後面的.Net8,9,10。在你面前毫無神秘可言。