記一次 .NET某上位視覺程序離奇崩潰分析
一、背景
1. 講故事
前段時(shí)間有位朋友找到我,說他們有一個(gè)崩潰的dump讓我?guī)兔聪略趺椿厥隆?/p>
話不多說,既然有 dump 來了,那就上 windbg 說話吧。
二:WinDbg 分析
1. 為什么會(huì)崩潰
說實(shí)話windbg非常強(qiáng)大,雙擊打開dump就能第一時(shí)間幫你顯示出簡略的異常信息,輸出如下:
This dump file has an exception of interest stored in it.
The stored exception information can be accessed via .ecxr.
(bf8.5dc4): Access violation - code c0000005 (first/second chance not available)
For analysis of this file, run !analyze -v
clr!WKS::gc_heap::mark_object_simple1+0x220:
00007ffb`380453c4 833a00          cmp     dword ptr [rdx],0 ds:00007ffa`35451300=????????從卦中又看到了經(jīng)典的 mark_object_simple1 方法,這個(gè)方法是GC用來做對(duì)象標(biāo)記之用的,所以大概率又是托管堆損壞,真是無語了,接下來用 !verifyheap 檢查下托管堆。
0:083> !verifyheap
object 00000218e96963d8: bad member 00000218E9696450 at 00000218E9696420
Last good object: 00000218E96963C0.
Could not request method table data for object 00000218E9696450 (MethodTable: 00007FFA35451300).
Last good object: 00000218E96963D8.一看這卦就很不吉利,真的是有對(duì)象的mt是不對(duì)的,至此我們把崩潰的直接原因給找到了。
2. 為什么對(duì)象損壞了
要找到這個(gè)答案就需要深挖 00000218e96963d8 對(duì)象,分別使用 !do 命令以及 dp 來觀察內(nèi)存地址。
0:083> !do 00000218e96963d8
Name:        System.Threading.Tasks.Task+DelayPromise
MethodTable: 00007ffb3542b3e8
EEClass:     00007ffb3567c7c0
Size:        120(0x78) bytes
File:        C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
...
00007ffb35451300  40035d5       48 ...m.Threading.Timer  0 instance 00000218e9696450 Timer
0:083> dp 00000218e9696450 L6
00000218`e9696450  00007ffa`35451301 00000000`00000000
00000218`e9696460  00000218`e96964c8 00000000`00000000
00000218`e9696470  00007ffb`353e4b51 00000218`e9696368仔細(xì)觀察卦中對(duì)象 00000218e9696450 所顯示的mt,你會(huì)發(fā)現(xiàn)一個(gè)是 00007ffb35451300,一個(gè)是 00007ffa35451301,很顯然前者是對(duì)的,后者是錯(cuò)的,可以分別用 !dumpmt 做個(gè)驗(yàn)證。
0:083> !dumpmt 00007ffb35451300
EEClass:         00007ffb356942f0
Module:          00007ffb353b1000
Name:            System.Threading.Timer
mdToken:         0000000002000504
File:            C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
BaseSize:        0x20
ComponentSize:   0x0
Slots in VTable: 23
Number of IFaces in IFaceMap: 1
0:083> !dumpmt 00007ffa35451301
00007ffa35451301 is not a MethodTable細(xì)心的朋友會(huì)發(fā)現(xiàn)雖然兩個(gè)mt地址不一樣,但已經(jīng)非常相近,看樣子又是一例經(jīng)典的bit位翻轉(zhuǎn),我去,用 .formats 轉(zhuǎn)成二進(jìn)制觀察一下,截圖如下:
圖片
從卦中可以清晰的看到當(dāng)前地址有兩個(gè) bit 的翻轉(zhuǎn),分別是第0位和第32位,接下來就要洞察為什么會(huì)有兩個(gè)bit位的翻轉(zhuǎn)?
3. 真的存在兩個(gè)bit位翻轉(zhuǎn)嗎
接下來我們逐一來聊一下。
- bit 0 為什么會(huì)翻轉(zhuǎn)
 
熟悉 coreclr 底層的朋友應(yīng)該知道,gc 在標(biāo)記的過程中會(huì)給 mt 的第0位設(shè)置為1,表示當(dāng)前對(duì)象在深度優(yōu)先中已經(jīng)標(biāo)記過,防止重復(fù)標(biāo)記,當(dāng)然這個(gè)也是有源碼作證的,簡化后的代碼如下:
inline BOOL gc_heap::gc_mark(uint8_t* o, uint8_t* low, uint8_t* high, int condemned_gen)
{
 if ((o >= low) && (o < high))
 {
  BOOL already_marked = marked(o);
  if (already_marked)
  {
   return FALSE;
  }
  set_marked(o);
  
  return TRUE;
 }
}
#define marked(i) header(i)->IsMarked()
BOOL IsMarked() const
{
 return !!(((size_t)RawGetMethodTable()) & GC_MARKED);
}有了這段源碼,這個(gè) bit 為什么為 1 就能輕松的解釋了,所以這個(gè)翻轉(zhuǎn)是一個(gè)正常情況。
- bit 32 為什么會(huì)翻轉(zhuǎn)
 
這個(gè)是我無法解釋的,也正是因?yàn)檫@個(gè) bit32 的翻轉(zhuǎn)導(dǎo)致 gc 認(rèn)為這個(gè) obj 是一個(gè)損壞的對(duì)象,到底是什么原因呢?民間眾說紛紜,在我的過往分析旅程中我已見過兩例,但我不敢確定自己又遇到了輻射類的奇葩情況,所以也第一時(shí)間找朋友確認(rèn)程序周邊是否存在輻射環(huán)境。
圖片
朋友反饋過來附近有 伺服電機(jī) 類,說實(shí)話工控的東西我是真的不太懂,只能上網(wǎng)搜搜這玩意是否有輻射,截圖如下:
圖片
到底是不是這玩意導(dǎo)致的,其實(shí)我心里也沒底,跟朋友的溝通后說是只出現(xiàn)過一次,這就更加玄乎了。
圖片
不管怎么說,我只能給出如下兩個(gè)方案:
- 上 ECC 糾錯(cuò)內(nèi)存
 - 遠(yuǎn)離輻射環(huán)境
 
三:總結(jié)
在大工控領(lǐng)域里,這是我見過第三例bit位翻轉(zhuǎn)導(dǎo)致的程序崩潰,太無語了,惡魔到底是不是旁邊的 伺服電機(jī) ?















 
 
 



















 
 
 
 