UE4 內存寫壞導致異常崩潰問題記錄

1. 問題表現

經常出現進程崩潰,崩潰堆棧較爲底層

image.png
原因基本上都是 read write memory 時觸發了異常,盤查後初步懷疑是內存寫壞了。

2. 排查期

UE 支持各種內存分配器:

  • TBB
  • Ansi
  • Jemalloc
  • Stomp
    還有自帶的內存分配器:
  • Binned
  • Binned2
  • Binned3
    可以參考文章 UE 中的內存分配器
    其中 Stomp 是引擎提供的排查內存寫壞的工具之一,通過增加參數 -stompmalloc 可以讓 UE 默認採用該內存分配器,啓用了之後崩潰的第一現場就是內存寫壞的代碼地址。
    通過排查發現崩潰原因是遍歷迭代器時刪除元素後沒有及時 continue,大致示例如下:
for(TArray<Actor*>::TIterator Iter(Actors); Iter; ++ Iter)
{
if (xxx)
{
Iter.RemoveCurrent(); //沒有 continue
}

if (xxx)
{
Iter.RemoveCurrent();//沒有 continue
}
}

當數組元素只剩一個時,如果觸發了兩次 RemoveCurrent,就會導致寫到數組之外的內存空間,RemoveCurrent 的機制會把後面的數組元素遷移到刪除的位置上,保證數據連貫。同時 RemoveCurrent 完畢後會自動把迭代器的下標前移一位。

image.png

3. Stomp 原理

3.1 內存覆蓋

Stomp 其主要的功能是在寫壞內存時可以馬上捕獲到第一現場。內存寫壞了通常指程序在操作內存時寫入了非法的數據或超出了內存分配的範圍,導致程序出現錯誤或崩潰。這種情況通常被稱爲越界訪問或非法訪問內存。
大部分情況下有內存池的技術,且操作系統分配內存往往會向上按頁對其分配,所以一時的內存越界讀寫有可能不會馬上出現問題。而 Stomp 是在內存越界時就對其拋出異常。

3.2 實現原理

開啓 Stomp 之後,內存分配基本上由 FMallocStomp::MallocFMallocStomp::Free 接管。

void* FMallocStomp::Malloc(SIZE_T Size, uint32 Alignment)
void FMallocStomp::Free(void* InPtr)

要做到寫壞內存後能直接觸發異常,需要在內存分配上做手腳,這裏主要用到了兩點:

  • 操作系統支持的 Pagefault 和 Page 權限控制
  • 哨兵機制
    Stomp 在給用戶分配內存的時候會額外分配 2 個 Page 出來,分別在返回給用戶的指針地址空間前後。當用戶超出分配給他的內存上讀寫時,就會觸發異常。其分配內存的流程大致如下:

image.png

這裏有個問題是 FAllocationData 只有 32 個字節,但是其分配了一個 Page 給其使用,這裏主要是由於分配內存都需要對齊 Page。到此內存分配完畢,接下來有 2 種情況:

  1. 從 Page2 寫數據一直寫到 Page3,由於 Page3 被標記爲不可讀不可寫,因此一旦出現越界,就會直接拋出異常
  2. 從 Page1 寫數據一直寫到 Page0,由於 Page0 末端分配了一個 FAllocationData,因此一旦越界,哨兵值會被覆蓋,當釋放內存時FMallocStomp::Free 就會對內存塊的 FAllocationData 進行檢查,一旦哨兵比對異常就拋出異常

image.png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章