Address Sanitizer 簡介

C++ 之痛

由於 C++ 這門編程語言與硬件(主要是內存)非常貼近,在 C++ 編程中經常遇到的的一個問題就是內存錯誤,其中可能包括:

  1. 內存泄漏:忘記 free 之前在堆中申請的內存,並丟失了所申請內存的指針;
  2. 內存訪問越界:包括對全局內存、棧內存、堆內存訪問的越界;
  3. 釋放後使用:訪問已經被 free 的內存;
  4. 返回後使用:訪問已經返回的函數棧中的內存;
  5. ……

這些錯誤中,有的會在程序運行的過程中報錯並使程序終止,這還算好的;有的則根本不會報錯,暗中影響程序的正確性。

Address Sanitizer 應運而生

因此,Google 開發了一款專門用於檢測內存訪問錯誤的工具:AddressSanitizer(簡稱 ASan),它可以自動檢測程序運行時(runtime)發生的許多內存訪問錯誤。

你不需要專門安裝它,因爲它被集成到了 LLVM 3.1 版本及以後、GCC 4.8 版本及以後了,所以你可以直接讓 Clang 或 GCC 打開 AddressSanitizer。比如,寫一個最簡單的內存訪問越界的程序:

#include <iostream>
#include <vector>

using namespace std;

int main() {
  vector<int> v(4, 123);  // 長度爲 4 的 vector
  cout << v[6] << "\n";   // 訪問越界
  return 0;
}

編譯的時候打開-fsanitize=address開關:

clang++ -fsanitize=address -o main main.cpp

編譯完成以後,運行程序,就會出現這樣的情況:

圖片.png

第一行紅色報錯:heap-buffer-overflow,說明是堆上的內存訪問越界了。確實,因爲vector是被分配在堆上的。

再看第二行藍色,說是線程T0READ一塊兒長度爲 4 (一個int的大小)的內存的時候發生的錯誤,緊跟着下面幾行就是錯誤處的調用棧了:

    #0 0x10237ae4c in main main.cpp:8
    #1 0x10252d0f0 in start+0x204 (dyld:arm64e+0x50f0)

看到棧頂的元素#0就是main.cpp:8,說明是 main.cpp 源代碼的第 8 行出現了錯誤,就是cout << v[6] << "\n";這一句話。這時就已經定位到發生錯誤的地方了。

如果不使用 AddressSanitizer,這個程序很有可能“正常運行”,即輸出v[6]0,讓人以爲這個程序沒問題。但實際上程序是錯誤的。這種錯誤單靠 Debugger 是找不出來的,或者說很難找出來。

LeetCode 對 AddressSanitizer 的應用

LeetCode 在編譯代碼的時候也會自動開啓 AddressSanitizer 來檢測內存訪問越界釋放後使用錯誤:

因此,你很可能在 LeetCode 上運行的時候會報出這樣看不懂的 runtime error:

這裏的報錯信息較爲簡陋,因此你可以在自己本機編譯調試的時候加上-fsanitize=address選項來打開 AddressSanitizer,在本機運行,就可以看到更爲詳細的報錯。注意,最好再加上-g選項,這樣程序中就會帶有調試符號和源碼行號,報錯信息中就會顯示到底是程序中的哪一個函數的哪一行發生了內存訪問錯誤。

結語

本文僅僅介紹了 Address Sanitizer 最基本的概念和使用方法。如果你對它的詳細用法和底層原理很感興趣,最好的閱讀材料就是官方 Wiki:https://github.com/google/sanitizers/wiki。還有其他的常用的內存檢測工具,如 Valgrind Memcheck

參考

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