WinDbg+Rotor解析WinForm調(diào)用堆棧及實現(xiàn)
前段寫過一篇文章“CLR探索系列:深入追蹤托管exe加載執(zhí)行過程”,在那篇文章中,主要是側(cè)重靜態(tài)代碼的分析,追蹤源代碼的流程一步一步看是如何實現(xiàn)的。
這次,寫一篇文章,結(jié)合Windbg,從一個托管應(yīng)用程序執(zhí)行的調(diào)用堆棧開始,追蹤其調(diào)用堆棧中的線索,以及這些托管應(yīng)用程序執(zhí)行中調(diào)用的功能實現(xiàn),來展示托管代碼的加載和執(zhí)行的流程和實現(xiàn)。
首先還是找一個小白鼠:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
System.Object o = new object();
lock (o)
{
System.GC.Collect();
}
}
}
這里找的是一個WinForm應(yīng)用程序來作為小白鼠。
咱就不采用在調(diào)用的關(guān)鍵的mscorwks和MscoeEE的方法上面下斷點(diǎn)來跟蹤,然后解釋每個斷點(diǎn)調(diào)用堆棧和環(huán)境的解釋方法了,直接把main thread的call stack給打出來一行一行的去挖掘好了。
打開Windbg,附加到進(jìn)程,加載好相關(guān)的symbol和2.0的SOS,切換到第0個Thread然后輸出其調(diào)用堆棧:
0:000> k
ChildEBP RetAddr
0012f4a0 7c92e9ab ntdll!KiFastSystemCallRet
0012fa48
0012ff18
0012ff68
0012ffb0 79011b
0012ffc0
0012fff0 00000000 KERNEL32!BaseProcessStart+0x23
BaseProcessStart表示的是運(yùn)行的Winform啟動的進(jìn)程。在前面的上一篇文章里面已經(jīng)分析過,并且用相關(guān)的工具查看過,一個托管模塊開始運(yùn)行的時候只想的是_CorExeMain方法。這是mscoree里面的一個方法。而mscoree只是選擇加載CLR版本的一個Loader。
之后,就跳轉(zhuǎn)到了選擇了特定版本了的mscorwks里面的_CorExeMain中:
0012ffb0 79011b
在sscli中,這個程序的名字就換成了_CorExeMain2來顯示對商業(yè)版本的區(qū)別。打開CorExeMain的定義:
__int32 STDMETHODCALLTYPE _CorExeMain2( // Executable exit code.
PBYTE pUnmappedPE, // -> memory mapped code
DWORD cUnmappedPE, // Size of memory mapped code
__in LPWSTR pImageNameIn, // -> Executable Name
__in LPWSTR pLoadersFileName, // -> Loaders Name
__in LPWSTR pCmdLine) // -> Command Line
在Load這個應(yīng)用程序的image的時候,這個entry point是從native entry point中被call的。在_CorExeMain2中,我們可以看到如下部分屬性和方法:
__int32 STDMETHODCALLTYPE _CorExeMain2( …)
{
// This entry point is used by clix
BOOL bRetVal = 0;
// Before we initialize the EE, make sure we've snooped for all EE-specific
// command line arguments that might guide our startup.
HRESULT result = CorCommandLine::SetArgvW(pCmdLine);
if (!CacheCommandLine(pCmdLine, CorCommandLine::GetArgvW(NULL))) {
LOG((LF_STARTUP, LL_INFO10, "Program exiting - CacheCommandLine failed\n"));
bRetVal = -1;
goto exit;
}
if (SUCCEEDED(result))
result = CoInitializeEE(COINITEE_DEFAULT | COINITEE_MAIN);
if (FAILED(result)) {
VMDumpCOMErrors(result);
SetLatchedExitCode (-1);
goto exit;
}
// Load the executable
bRetVal = ExecuteEXE(pImageNameIn);
if (!bRetVal) {
//這里,如果出現(xiàn)錯誤的話,可能的原因不正確的metadata文件的格式,或者其版本,可能是load的mscorwks的版本不正確造成的。也可能是signed assemblies和對應(yīng)的錯誤處理程序不匹配造成的。總之,運(yùn)行正確的話,是不會走到這里的。這個地方也可以作為CLR在開發(fā)的時候調(diào)試下斷點(diǎn)的一個地方。
EEMessageBoxCatastrophic(IDS_EE_COREXEMAIN2_FAILED_TEXT, IDS_EE_COREXEMAIN2_FAILED_TITLE);
SetLatchedExitCode (-1);
}
//當(dāng)程序走到這個地方的時候,it is the time to shut off the lights and went home了。這些都是程序退出的時候的執(zhí)行的動作。
exit:
STRESS_LOG1(LF_STARTUP, LL_ALWAYS, "Program exiting: return code = %d", GetLatchedExitCode());
STRESS_LOG0(LF_STARTUP, LL_INFO10, "EEShutDown invoked from _CorExeMain2");
EEPolicy::HandleExitProcess();
//END_ENTRYPOINT_VOIDRET;
return bRetVal;
}
OK,從上面的程序里面,我們大概看到了一個托管模塊的生命周期。所以,最關(guān)鍵的一句就在這里了:
bRetVal = ExecuteEXE(pImageNameIn);
這一行,也就是對應(yīng)這上面堆棧調(diào)用的倒數(shù)第四行了。
接下來讓我們看看這個ExecuteEXE方法都做了些什么吧:
BOOL STDMETHODCALLTYPE ExecuteEXE(HMODULE hMod)
{
if (!hMod)
return FALSE;
ETWTraceStartup::TraceEvent(ETW_TYPE_STARTUP_EXEC_EXE);
TIMELINE_START(STARTUP, ("ExecuteExe"));
EX_TRY_NOCATCH
{
// Executables are part of the system domain
SystemDomain::ExecuteMainMethod(hMod);
}
return TRUE;
}
運(yùn)行到這里,就可以看到,SystemDomain已經(jīng)啟動,同時開始執(zhí)行Main方法。繼續(xù)查看上面的調(diào)用堆棧:
0012fa48
0012ff18
可以看到,繼SystemDomain之后,又將Assembly load到了Domain中,最后是用ClassLoader來執(zhí)行Main程序。這里就不一一展示其實現(xiàn)了。
在執(zhí)行了RunMain方法的時候,這時下面的三個堆棧:
在前面的上一篇文章中已經(jīng)說過,MethodDesc,methodtable,EEClass,MethodDescChunk這些結(jié)構(gòu)的關(guān)系和區(qū)別。MethodDesc是CLR中對應(yīng)的托管方法的非托管的結(jié)構(gòu)。
下面的三個方法,主要是實現(xiàn)了定位和尋找Call Target,負(fù)責(zé)托管代碼的編譯等工作。
CallDescrWorkerWithHandler是調(diào)用的一個外部C語言編寫的函數(shù),Call這個方法的目的,是為了把MethodTable與操作系統(tǒng)平臺相關(guān)的Exception Handle程序聯(lián)系起來。
最后的堆棧最上面的幾行:
這個地方首先調(diào)用了System.Windows.Forms.NI中來初始化Winform的顯示。這里的ni后綴表面調(diào)用的是nGen函數(shù)。最后停在USER32!NtUserWaitMessage上面,等待用戶的操作。
這里的分析,只是展示了一個WinForm在執(zhí)行完畢之后的調(diào)用堆棧。里面有很多和Thread,JIT和GC相關(guān)的功能的初始化和額外線程的啟動,譬如,主線程創(chuàng)建FinalizerThread和GCThread,由于沒有下斷點(diǎn)跟蹤,所以這里都沒有展現(xiàn)出來。
此文的主要目的,在于提供一種閱讀和分析Rotor的方法,讓對Rotor(sscli)的分析,不僅僅限制與對靜態(tài)代碼的分析,我們還可以結(jié)合DotNet應(yīng)用程序的運(yùn)行,動態(tài)的分析代碼的執(zhí)行和實現(xiàn)。
同時,研究不同類型應(yīng)用程序調(diào)用堆棧,里面還有非常多有意思的東西可以發(fā)掘。
3/25/2008 10:38:33 AM
posted on 2008-03-25 10:50 lbq1221119 閱讀(3762) 評論(2) 收藏 舉報
浙公網(wǎng)安備 33010602011771號