錯誤: container-overflow
位址清理程式錯誤:容器溢位
在 Visual Studio 2022 17.2 版和更新版本中,Microsoft Visual C++ 標準連結庫 (STL) 已部分啟用,以使用 AddressSanitizer。 下列容器類型具有可偵測記憶體存取問題的批註:
標準容器類型 | 停用註釋巨集 | 版本支援 |
---|---|---|
std::vector |
_DISABLE_VECTOR_ANNOTATION |
Visual Studio 2022 17.2 |
std::string |
_DISABLE_STRING_ANNOTATION |
Visual Studio 2022 17.6 |
有檢查可確保沒有單一定義規則 (ODR) 違規。 當一個轉譯單位使用 ASan 註釋來標註標準類型時, vector
就會發生 ODR 違規,但另一個轉譯單位則不會。 在此範例中,連結器可能會同時看到具有位址清理工具註釋的其中一個宣告 vector<int>::push_back
,而另一個 vector<int>::push_back
宣告則不會。 若要避免這個問題,用來連結二進位檔的每個靜態庫和對象也必須啟用 ASan 註釋。 實際上,您必須使用已啟用 AddressSanitizer 來建置這些靜態連結庫和物件。 混合程式代碼與不同的批注設定會導致錯誤:
my_static.lib(my_code.obj) : error LNK2038: mismatch detected for 'annotate_vector': value '0' doesn't match value '1' in main.obj
若要解決此錯誤,請停用使用對應巨集的所有專案中的註釋,或使用和註釋來建置每個專案 /fsanitize=address
。 (註釋預設為啟用。
範例:存取 中的保留記憶體 std::vector
// Compile with: cl /EHsc /fsanitize=address /Zi
#include <vector>
int main() {
// Create a vector of size 10, but with a capacity of 20.
std::vector<int> v(10);
v.reserve(20);
// In versions prior to 17.2, MSVC ASan does NOT raise an exception here.
// While this is an out-of-bounds write to 'v', MSVC ASan
// ensures the write is within the heap allocation size (20).
// With 17.2 and later, MSVC ASan will raise a 'container-overflow' exception:
// ==18364==ERROR: AddressSanitizer: container-overflow on address 0x1263cb8a0048 at pc 0x7ff6466411ab bp 0x005cf81ef7b0 sp 0x005cf81ef7b8
v[10] = 1;
// Regardless of version, MSVC ASan DOES raise an exception here, as this write
// is out of bounds from the heap allocation.
v[20] = 1;
}
若要建置及測試此範例,請在 Visual Studio 2022 17.2 版或更新版本的 開發人員命令提示 字元視窗中執行下列命令:
cl /EHsc example1.cpp /fsanitize=address /Zi
devenv /debugexe example1.exe
中保留記憶體存取的錯誤結果 std::vector
自訂配置器和容器溢位
位址清理器容器溢位檢查支援非std::allocator
配置器。 不過,由於 AddressSanitizer 不知道自定義配置器是否符合 AddressSanitizer 需求,例如在 8 位元組界限上對齊配置,或未在配置結尾與下一個 8 位元組界限之間放置數據,因此不一定能夠檢查配置後端的存取是否正確。
AddressSanitizer 會在 8 位元組區塊中標記記憶體區塊。 在單一區塊中可存取的位元組之前,它無法放置無法存取的位元組。 區塊中有 8 個可存取的位元組,或 4 個可存取的位元組,後面接著 4 個無法存取的位元組是有效的。 四個無法存取的位元組不能後面接著4個可存取的位元組。
如果自定義配置器配置器的結尾與8位元組區塊的結尾不一致,AddressSanitizer 必須假設配置器會在配置結尾與配置器或使用者寫入的區塊結尾之間建立位元組。 因此,它無法將最後8位元組區塊中的位元組標示為無法存取。 在下列使用自定義配置器配置記憶體的 vector
範例中,'?' 是指未初始化的數據,而 '-' 是指無法存取的記憶體。
std::vector<uint8_t, MyCustomAlloc<uint8_t>> v;
v.reserve(20);
v.assign({0, 1, 2, 3});
// the buffer of `v` is as follows:
// | v.data()
// | | v.data() + v.size()
// | | | v.data() + v.capacity()
// [ 0 1 2 3 ? ? ? ? ][ ? ? ? ? ? ? ? ? ][ ? ? ? ? - - - - ]
// chunk 1 chunk 2 chunk 3
在上一個範例中,區塊 3 有 4 個字節的記憶體,假設無法存取,因為它們會落在已保留的 20 個字節配置結尾和 8 個字節的第三個邏輯群組結尾之間v.reserve(20)
(請記住 AddressSanitizer 會標示 8 位元組區塊中的記憶體區塊)。
在理想情況下,我們會標記陰影記憶體,Address Sanitizer 會針對每 8 位元組的記憶體區塊保留一個,以追蹤該 8 位元組區塊中的哪些位元組有效且無效(以及原因),因此 v.data() + [0, v.size())
可以存取且 v.data() + [v.size(), v.capacity())
無法存取。 請注意,此處使用間隔表示法:『[' 表示包含 ,而 『)』 表示專屬。 如果使用者使用自定義配置器,我們不知道之後 v.data() + v.capacity()
的記憶體是否可供存取。 我們必須假設它是。 我們偏好將這些位元組標示為在陰影記憶體中無法存取,但我們必須將它們標示為可存取,以便在配置之後仍可存取這些位元組。
std::allocator
會 _Minimum_asan_allocation_alignment
使用靜態成員變數來告知 vector
,而且 string
他們可以信任配置器不要在配置后直接放置數據。 這可確保配置器不會在配置結尾和區塊結尾之間使用記憶體。 因此,Address Sanitizer 可以標示區塊的一部分無法存取,以攔截滿溢。
如果您想要實作信任您的自定義配置器正在處理配置結尾與區塊結尾之間的記憶體,讓它可以將該記憶體標示為無法存取並攔截滿溢,請將 設定 _Minimum_asan_allocation_alignment
為實際的最小對齊方式。 若要讓 AddressSanitizer 正常運作,對齊方式必須至少為 8。
另請參閱
AddressSanitizer 概觀
AddressSanitizer 已知問題
AddressSanitizer 組建和語言參考
AddressSanitizer 運行時間參考
AddressSanitizer 陰影位元組
AddressSanitizer 雲端或分散式測試
AddressSanitizer 調試程式整合
AddressSanitizer 錯誤範例