除錯與工具

位址消毒器(AddressSanitizer, ASan)

/ ASan -> AY-san /

想像一下,如果不是事後把你的程式跑在一個慢吞吞的模擬器裡,而是編譯器在建構你程式時,就在每一次記憶體存取裡接上一個微型防盜警報——這樣你一碰到不該碰的記憶體,警報就響、並準確告訴你在哪裡。位址消毒器就是那個內建的警報。你在編譯時加上一個旗標,你的程式現在就會在執行時偵測記憶體錯誤,速度接近正常,並在出錯的瞬間大聲當掉、附上精確的報告。

具體來說,ASan 是內建於 gcc 與 clang 的一種消毒器。你用 -fsanitize=address 編譯(通常搭配 -g 以得到行號),編譯器就對程式碼插樁:它在每一塊堆積與堆疊配置周圍圍上下毒的「紅區」,並維護一張影子映射、記錄哪些位元組可以合法存取。每一次載入與儲存都會對照那張影子檢查。如果你的程式碼讀寫進一個紅區(緩衝區溢位)、在記憶體被釋放之後碰它(釋放後使用)、在函式返回之後使用一個堆疊變數(返回後使用堆疊)、或釋放兩次,ASan 會立刻停下、並印出詳細報告:錯誤種類、出錯的位址,以及那次壞存取、那次配置與那次釋放的堆疊追蹤。代價不大——大約慢 2 倍、記憶體用得多一些——因為這些檢查是被編譯進去、而非被解譯。

為何重要:ASan 逮到最危險的 C 臭蟲——溢位與釋放後使用,這些原本會默默破壞記憶體、過很久才當掉——而且它夠快,可以在你整套測試套件上一直開著,所以臭蟲在壞存取第一次發生時就被逮到,而不是透過一場延遲、神秘的當機。誠實的提醒:它必須被編譯進去,所以它只檢查你用它建構的程式碼(不含第三方執行檔);它只在你測試實際執行到的路徑上找錯;它預設不會逮到未初始化的讀取(那是 MemorySanitizer)或資料競爭(ThreadSanitizer),而且你無法在一次建構裡同時用 ASan 與 TSan。對付記憶體破壞臭蟲,它是現代的第一選擇,以遠少的速度損失補足 Valgrind。

$ gcc -g -fsanitize=address overflow.c -o a 接著 $ ./a 對一段把 arr[10] 寫進 10 元素陣列的程式碼,會印出「ERROR: AddressSanitizer: stack-buffer-overflow ... WRITE of size 4 at ... in main overflow.c:8」並立刻中止——沒有延遲、神秘的當機。

用 -fsanitize=address 編譯;那次壞寫入會在確切的行號被立即回報。

ASan 只對你用它編譯的程式碼插樁,也只在你執行實際走到的路徑上逮錯——一次乾淨的 ASan 執行意味著「在這些輸入上沒有錯」,而非「可證明無臭蟲」。它不會偵測未初始化的讀取或資料競爭,而且 ASan 與 ThreadSanitizer 無法同時啟用。

又稱
ASan-fsanitize=addressaddress sanitizer