前段時間收到了一個朋友的求助,說他的ERP網站系統會出現偶發性崩潰,找了好久也沒找到是什麼原因,讓我幫忙看下,其實崩潰好說,用 procdump 自動抓一個就好,拿到 dump 之後,接下來就是一頓分析了。
windbg 有一個自動化的分析命令 !analyze -v
可以幫我們提前預診一下,就好像進醫院先在問詢臺那裡過一下。
0:019> !analyze -v
CONTEXT: (.ecxr)
eax=14c9cd00 ebx=00000000 ecx=00000000 edx=00000000 esi=00000000 edi=14c9d664
eip=682a024a esp=14c9cfd4 ebp=14c9d018 iopl=0 nv up ei pl nz ac po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212
msvcr90!fprintf+0x34:
682a024a 83c414 add esp,14h
Resetting default scope
EXCEPTION_RECORD: (.exr -1)
ExceptionAddress: 682a024a (msvcr90!fprintf+0x00000034)
ExceptionCode: c0000417
ExceptionFlags: 00000001
NumberParameters: 0
PROCESS_NAME: w3wp.exe
ERROR_CODE: (NTSTATUS) 0xc0000417 - C
EXCEPTION_CODE_STR: c0000417
STACK_TEXT:
14c9d018 1766013b 00000000 176d9c60 17def1a8 msvcr90!fprintf+0x34
WARNING: Stack unwind information not available. Following frames may be wrong.
14c9d664 454c5153 75636578 203a6574 5332347b satrda!Writer_Write+0x4bb
000000c8 75636578 203a6574 5332347b 207d3230 0x454c5153
000000c8 17673623 17d538e8 17ded730 00000001 crypt32!profapi_NULL_THUNK_DATA_DLA <PERF> (crypt32+0x126578)
00000009 176604b6 14c9d74c 17ded730 17dae9c8 satrda!SATRDA_Proto_UnitTest+0x6c93
ffffffff 17654012 17dae9c8 17d538e8 17ded730 satrda!Writer_Write+0x836
17dae9c8 665fe072 14c9d74c 00000001 1765405b satrda!ConfigDSN+0xd0c2
...
160a0000 00000000 00000000 00000000 00000000 0x7071e31
FAULTING_SOURCE_LINE: f:\dd\vctools\crt_bld\self_x86\crt\src\fprintf.c
FAULTING_SOURCE_FILE: f:\dd\vctools\crt_bld\self_x86\crt\src\fprintf.c
FAULTING_SOURCE_LINE_NUMBER: 55
FAULTING_SOURCE_CODE:
No source found for 'f:\dd\vctools\crt_bld\self_x86\crt\src\fprintf.c'
SYMBOL_NAME: msvcr90!fprintf+34
MODULE_NAME: msvcr90
IMAGE_NAME: msvcr90.dll
STACK_COMMAND: ~19s; .ecxr ; kb
FAILURE_BUCKET_ID: INVALID_CRUNTIME_PARAMETER_c0000417_msvcr90.dll!fprintf
從錯誤資訊看,問題是出在 satrda.dll
這個第三方庫,趕緊網上搜一下是這是何方神聖。
看樣子是一個連線資料庫的商業元件,接下來看下 FAILURE_BUCKET_ID: INVALID_CRUNTIME_PARAMETER_c0000417_msvcr90.dll!fprintf
資訊,可以發現因為在呼叫 fprintf
函數時出現了引數錯誤,到這裡我們將包圍圈極大的收縮了。
熟悉 C 語言 fprintf
函數的朋友都知道,它是用來向 檔案
寫入資料的,類似 C# 的 WriteFile
,既然報了引數異常,那就說明肯定在引數上出了問題,接下來看下它的簽名。
int fprintf(
FILE *stream,
const char *format [,
argument ]...
);
有了這些基礎之後切到 19
號執行緒觀察下它的呼叫棧。
0:019> ~19s; .ecxr ; kb 10
eax=14c9cd00 ebx=00000000 ecx=00000000 edx=00000000 esi=00000000 edi=14c9d664
eip=682a024a esp=14c9cfd4 ebp=14c9d018 iopl=0 nv up ei pl nz ac po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212
msvcr90!fprintf+0x34:
682a024a 83c414 add esp,14h
# ChildEBP RetAddr Args to Child
00 14c9d018 1766013b 00000000 176d9c60 17def1a8 msvcr90!fprintf+0x34 [f:\dd\vctools\crt_bld\self_x86\crt\src\fprintf.c @ 55]
WARNING: Stack unwind information not available. Following frames may be wrong.
01 14c9d664 454c5153 75636578 203a6574 5332347b satrda!Writer_Write+0x4bb
02 000000c8 75636578 203a6574 5332347b 207d3230 0x454c5153
03 000000c8 17673623 17d538e8 17ded730 00000001 crypt32!profapi_NULL_THUNK_DATA_DLA <PERF> (crypt32+0x126578)
04 00000009 176604b6 14c9d74c 17ded730 17dae9c8 satrda!SATRDA_Proto_UnitTest+0x6c93
05 ffffffff 17654012 17dae9c8 17d538e8 17ded730 satrda!Writer_Write+0x836
06 17dae9c8 665fe072 14c9d74c 00000001 1765405b satrda!ConfigDSN+0xd0c2
07 17ded730 63207463 2c44492e 6f532e63 632c7472 clr!CompressDebugInfo::CompressBoundariesAndVars+0x2d0
08 656c6573 2c44492e 6f532e63 632c7472 7261502e 0x63207463
09 656c6573 6f532e63 632c7472 7261502e 49746e65 0x2c44492e
0a 656c6573 69482e63 6e656464 4c2e632c 6c657665 Microsoft_Build_Tasks_v4_0_ni+0x2f2e63
0b 2c687461 6e656464 4c2e632c 6c657665 64646948 System_ServiceModel_Web_ni+0xf2e63
0c 69482e63 4c2e632c 6c657665 64646948 632c6e65 System_Runtime_Serialization_ni+0x226464
0d 6e656464 6c657665 64646948 632c6e65 6d6f432e 0x4c2e632c
0e 6e656464 64646948 632c6e65 6d6f432e 656e6f70 System_ServiceModel_ni+0x537665
0f 6c657665 632c6e65 6d6f432e 656e6f70 632c746e 0x64646948
從執行緒棧來看 msvcr90!fprintf
函數的第一個引數居然是 00000000
,也就是說 *stream
這個引數為 NULL,難怪說引數異常!
熟悉 C 的朋友應該知道 *stream
引數是通過 fopen
函數得到的,可能有些朋友有點混,這裡就寫個簡單的模型吧。
int main()
{
FILE* pFile;
int n;
char name[100];
pFile = fopen("D:\\dumps\\myfile2.txt", "w");
gets_s(name, 100);
fprintf(pFile, "%s", name);
fclose(pFile);
return 0;
}
接下來我們到 dump 中尋找一下 fopen
函數,這個線上程棧上是沒有了,先提取出 msvcr90!fprintf+0x34
中的 RetAddr=1766013b
返回值地址到組合視窗查詢,截圖如下:
從圖中可以看到,esi 是 eax 給的,而 eax 是 call 返回值給的,不出意外 176D727Ch
中存的就是 fopen
函數,輸出如下:
0:019> u poi(176D727Ch)
msvcr90!fopen [f:\dd\vctools\crt_bld\self_x86\crt\src\fopen.c @ 123]:
682a01a2 8bff mov edi,edi
682a01a4 55 push ebp
682a01a5 8bec mov ebp,esp
682a01a7 6a40 push 40h
682a01a9 ff750c push dword ptr [ebp+0Ch]
682a01ac ff7508 push dword ptr [ebp+8]
682a01af e825ffffff call msvcr90!_fsopen (682a00d9)
682a01b4 83c40c add esp,0Ch
接下來我們需要提取 fopen
中的兩個引數,截圖如下:
第二個引數很好獲取就是 176D9C60h
的 ascii 表示,第一個引數獲取起來就麻煩了,我們需要詳細的如圖那樣推測當時的 esp 指向的位置。
0:019> da 14c9d074
14c9d074 "0810"
0:019> da 176D9C64h
176d9c64 "at++"
還原成 C 程式碼大概就是:
FILE* pFile = fopen("0810", "at++");
程式碼大概是恢復出來了,那為什麼會拋異常呢? windbg 有一個 !gle
命令可以檢視當時發生了什麼錯誤。
0:019> !gle
LastErrorValue: (NTSTATUS) 0 (0) - STATUS_SUCCESS
LastStatusValue: (NTSTATUS) 0xc000003a - { } %hs
接下來到微軟的官方檔案:https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55
找一下這個 3a 到底表示啥意思,截圖如下:
從圖中看,原來是路徑不存在的錯誤,應該就是沒找到 0810
這個檔案。
到這裡就基本弄清楚了來龍去脈,應該是朋友的伺服器有意或者無意清理了由 satrda
生成的 0810 檔案,引發 satrda.dll
找不到檔案路徑導致的程式崩潰,將這些資訊提供給朋友之後,讓朋友去找 satrda
官網去了解下詳情,畢竟官方才是最清楚的。
這次事故是由於 satrda
層面找不到檔案路徑導致的程式崩潰,據朋友說在 C# 層面沒收到這種C++異常,確實當 C# 和 C++ 產生互動時經常會有各種奇怪的問題,我無意刪除你的,你無意干擾我的,大家都好自為之吧