Sunday, July 10, 2011

Windbg: oleaut32!DllMain+0x76 除錯筆記

最近看到了一個令人匪夷所思的 Crash Dump,Call Stack 是長這樣:

0c 078efe7c 770d15a8 oleaut32!DllMain+0x76 (Unloaded)
0d 078efe9c 7c94118a oleaut32!_DllMainCRTStartup+0x52 (Unloaded)
0e 078efebc 7c953a23 ntdll!LdrpCallInitRoutine+0x14
0f 078eff34 7c80c126 ntdll!LdrShutdownThread+0xd7
10 078eff6c 7813299f kernel32!ExitThread+0x3e
11 078eff74 781329c0 msvcr80!_endthreadex+0x1f
12 078effac 78132a47 msvcr80!_callthreadstartex+0x20
13 078effb4 7c80b713 msvcr80!_threadstartex+0x66

在程式要回到 oleaut32!DllMain+0x76 的時候,就發生 Access Violation,原因是因為 oleaut32.dll 已經被 Unloaded 了。

花了一些時間追 oleaut32.dll 的實作,發現它在 InitAppData() 裡面會去呼叫 CoCreateInstanceEx(),這邊有個神祕的 Error Handling:

如果 Calling Thread 沒有執行過 CoInitialize(),它會回傳 0x800401F0h,也就是 COM 沒有初始化的錯誤碼。

在此時,oleaut32.dll 會在這個 Calling Thread 的 APP_DATA 中留下一個 Flag,當在 Thread 結束的時候,oleaut32.dll 就會在 DLL_THREAD_DETACH 裡面去檢查這個 Flag。如果 Flag 有被標記,那麼 oleaut32.dll 就會呼叫 ole32!CoSetState(0) 去重置 COM Thread  的狀態。

(APP_DATA 是放在 Thread Local Storage 的一個 Structure,每個有跑過 OLE 的 Thread 都應該會有一個。)

然而,CoSetState() 有個行為,如果有 State 被重置,就會把 oleaut32.dll 給 Unload 掉,可是此時 oleaut32.dll 就是 CoSetState() 的 Caller,當 CoSetState() 一回去之後,就 Access Violation。

(這裡有個更匪夷所思的實作,ole32.dll 是用 global variable 去存放 oleaut32.dll 的 handle。)

好啦,這些都是微軟的 Binary,遇到了這個問題該怎麼辦?根據分析,至少我們知道這一切都是起因於 OLE Automation 遇到了 COM 初始化的問題,所以至少可以先朝這個方向先 Troubleshooting。

[這篇文章是針對 Windows XP SP3 的環境做分析,Windows 7 的 ole32.dll 和 oleaut32.dll 已經拿掉了 CoSetState(),也許新的平台已經不會再有這種問題。]


參考資料: