常德網站建設案例教程seo培訓一對一
目錄
1、C++程序中的內存問題
2、AddressSanitizer是什么?
3、AddressSanitizer內存檢測原理簡述
3.1、內存映射
3.2、插樁
4、為什么選擇AddressSanitizer?
4.1、Valgrind介紹
4.2、AddressSanitizer在速度和內存方面為什么明顯優(yōu)于Valgrind
4.3、在很多實際項目中我們需要使用AddressSanitizer
5、無法使用Valgrind的具體項目實例
5.1、使用Valgrind檢測導致CPU占滿,無法進行檢測
5.2、使用Valgrind檢測導致程序運行過慢,無法進行檢測
6、AddressSanitizer與其他內存工具的比較
7、如何使用AddressSanitizer?
7.1、升級gcc版本
7.2、如何配置使用AddressSanitizer進行內存檢測
7.3、使用AddressSanitizer進行內存檢測的實例
7.4、使用AddressSanitizer的注意事項
8、Windows平臺高版本的Visual Studio也支持AddressSanitizer工具
VC++常用功能開發(fā)匯總(專欄文章列表,歡迎訂閱,持續(xù)更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++軟件異常排查從入門到精通系列教程(專欄文章列表,歡迎訂閱,持續(xù)更新...)
https://blog.csdn.net/chenlycly/article/details/125529931C++軟件分析工具從入門到精通案例集錦(專欄文章正在更新中...)
https://blog.csdn.net/chenlycly/article/details/131405795C/C++基礎與進階(專欄文章,持續(xù)更新中...)
https://blog.csdn.net/chenlycly/category_11931267.html? ? ? ?C++程序中大部分問題都是內存問題,有些可以快速定位,有些則很難排查,通過日志打印及代碼走讀很難定位,并且有些難纏的問題只在客戶的環(huán)境中才會出現(在公司內部測試環(huán)境中無法復現),處理起來非常頭疼,把人搞的精疲力竭后可能還查不出來。今天給大家介紹來自Google的強大C/C++內存檢測工具AddressSanitizer,它可以很好地解決實際應用環(huán)境中很多無法快速定位的內存問題。
1、C++程序中的內存問題
? ? ? ?在C++程序中,大部分程序運行異常都是內存問題引起的,內存問題也是最讓C++程序員頭疼的事情。常見的內存異常有空指針、野指針、線程棧溢出、內存越界(棧內存越界、堆內存越界和全局內存越界)、堆內存被破壞、內存泄漏、虛擬內存不足等,如下所示:
其中某些場景下的內存越界問題以及堆內存被破壞問題最為難查,特別是堆內存被破壞問題。一般堆內存被破壞會表現為,程序到處胡亂崩潰,一會崩在這里,一會崩在那里!因為堆內存被破壞,一會崩潰在new的地方,一會崩潰在delete的地方!
? ? ? ? 關于C++軟件異常及內存錯誤的詳細說明,我就不在此贅述了,感興趣的,可以去看看我之前寫的文章:
C++軟件異常分析概述https://blog.csdn.net/chenlycly/article/details/123991269引發(fā)C++程序內存錯誤的常見原因分析與總結
https://blog.csdn.net/chenlycly/article/details/128599525
2、AddressSanitizer是什么?
? ? ? ?AddressSanitizer(簡稱ASan)是google提供的一款面向C/C++語言的內存錯誤問題檢查工具,它可以檢測出堆溢出(Heap buffer overflow)、棧溢出(Stack buffer overflow)、全局變量越界(Global buffer overflow)、已釋放內存使用(Use after free )、初始化順序(Initialization order bugs)、內存泄漏(Use after free )等多個內存問題。
AddressSanitizer項目地址:AddressSanitizer · google/sanitizers Wiki · GitHub
參考文檔頁面:AddressSanitizerAlgorithm · google/sanitizers Wiki · GitHub
? ? ? ?AddressSanitizer相對于Valgrind要快很多,只拖慢程序兩倍左右。它包括一個編譯器instrumentation插樁模塊和一個提供malloc/free替代項的運行時庫。從gcc 4.8開始,AddressSanitizer成為gcc的一部分,使用時非常方便,只需要在編譯時指定編譯選項就可以了。gcc 4.8自帶的AddressSanitizer還不完善,有明顯的缺陷(比如當監(jiān)測到任何一個error,它就會強制退出主程序,導致程序無法繼續(xù)運行,再比如沒有符號信息),最好使用gcc 4.9及以上版本。
AddressSanitizer原先只支持Linux,現在也可以在Windows上使用了。微軟在Visual Studio 2019的16.9版本中引入了強大的內存分析工具AddressSanitizer。
3、AddressSanitizer內存檢測原理簡述
? ? ? ? AddressSanitizer主要由兩部分組成:一個是靜態(tài)插樁(Instrumentation)模塊,將內存訪問判斷的邏輯直接插入在了二進制中,保證了檢測邏輯的執(zhí)行速度;另一部分則是運行時庫(Run-time library),提供部分功能的開啟、報錯函數和 malloc/free/memcpy 等函數的ASan檢測版本。
? ? ? ?instrument靜態(tài)插樁模塊,對棧上對象、全局對象、動態(tài)分配的對象分配redzone,以及針對這些內存做訪問檢測。
? ? ? ?runtime 運行時庫提供了一些運行時的復雜的功能(比如poison/unpoison shadow memory),替換 malloc/free/memcpy/memset等實現,提供報錯函數,針對每一次內存讀寫,編譯器都會插入判斷邏輯,判斷地址是否被投毒(poisoned)。
該算法的思路是,如果要防住Buffer Overflow漏洞,只需要在每塊內存區(qū)域右端(或兩端,能防overflow和underflow)加一塊區(qū)域(RedZone),使RedZone的區(qū)域的影子內存(Shadow Memory)設置為不可寫即可。
3.1、內存映射
? ? ? ?AddressSanitizer保護的主要原理是對程序中的虛擬內存提供粗粒度的影子內存(每8個字節(jié)的內存對應一個字節(jié)的影子內存),為了減少overhead,采用了直接內存映射策略,所采用的具體策略如下:Shadow = (Mem >> 3) + offset。每8個字節(jié)的內存對應一個字節(jié)的影子內存:
?影子內存中每個字節(jié)存取一個數字k,如果k=0,則表示該影子內存對應的8個字節(jié)的內存都能訪問,如果0<k<7,表示前k個字節(jié)可以訪問,如果k為負數,不同的數字表示不同的錯誤(e.g. Stack buffer overflow, Heap buffer overflow)。
3.2、插樁
? ? ? ?為了防止buffer overflow,需要將原來分配的內存兩邊分配額外的內存Redzone,并將這兩邊的內存加鎖,設為不能訪問狀態(tài),這樣可以有效的防止buffer overflow(但不能杜絕buffer overflow)。插樁的簡化示意圖如下:
?? ? ? ? 以下是在棧中插樁的一個例子:
1)未插樁的代碼:
void foo()?
{char a[8];// ...return;
}
2)插樁后的代碼:
char redzone1[32]; // 32-byte aligned
char a[8]; ? ? ? ? // 32-byte aligned
char redzone2[24];
char redzone3[32]; // 32-byte aligned
int*shadow_base = MemToShadow(redzone1);
shadow_base[e] = oxffffffff;// poison redzone1
shadow_base[1] = oxffffffe0;// poison redzone2,unpoison 'a'
shadow_base[2] = oxffffffff;// poison redzone3
// ...
return;
? ? ? ?在動態(tài)運行庫中將malloc/free函數進行了替換。在malloc函數中額外的分配了Redzone區(qū)域的內存,將與Redzone區(qū)域對應的影子內存加鎖,主要的內存區(qū)域對應的影子內存不加鎖。free函數將所有分配的內存區(qū)域加鎖,并放到了隔離區(qū)域的隊列中(保證在一定的時間內不會再被malloc函數分配),可檢測Use after free類的問題。
4、為什么選擇AddressSanitizer?
? ? ? ?Linux平臺上常用的內存分析工具主要有Valgrind和AddressSanitizer,這兩個工具在使用方式上有一定的區(qū)別。Valgrind不需要重新編譯代碼,可以直接附加到程序上對內存進行監(jiān)測;AddressSanitizer則需要重新編譯代碼。所以,很多時候大家為了圖方便,會優(yōu)先使用Valgrind。
? ? ? ?但Valgrind會占用大量內存并明顯拖慢程序運行的速度,這使得在部分場景下無法正常使用Valgrind。而AddressSanitizer在運行速度和效率上要比Valgrind好很多,所以在Valgrind無法完成檢測時可以選擇AddressSanitizer。
4.1、Valgrind介紹
? ? ? ?Valgrind是一套Linux下開放源代碼(GPL V2)的仿真調試工具的集合,是運行在Linux 上的多用途代碼分析和內存調試常用工具。Valgrind由內核(core)以及基于內核的其他調試工具組成。內核類似于一個框架(framework),它模擬了一個CPU環(huán)境,并提供服務給其他工具;而其他工具則類似于插件 (plug-in),利用內核提供的服務完成各種特定的內存調試任務。
? ? ? ?Valgrind是基于仿真的方式對程序進行調試,它先于應用程序獲取實際處理器的控制權,并在實際處理器的基礎上仿真一個虛擬處理器,并使應用程序運行于這個虛擬處理器之上,從而對應用程序的運行進行監(jiān)視。
? ? ? ?應用程序并不知道該處理器是虛擬的還是實際的,已經編譯成二進制代碼的應用程序并不用重新進行編譯,Valgrind 直接解釋二進制代碼使得應用程序基于它運行,從而能夠檢查內存操作時可能出現的錯誤。所以,在Valgrind下運行的程序運行速度要慢的多,而且使用的內存比目標程序要多的多,這也是Valgrind的一大劣勢,這也導致部分場合下沒法使用Valgrind去分析。
4.2、AddressSanitizer在速度和內存方面為什么明顯優(yōu)于Valgrind
? ? ? ?Valgrind采用的是二進制完全映射的影子內存技術,會占用更多內存才能去有效地監(jiān)測內存變化。并且開啟Valgrind監(jiān)測之后,會嚴重降速,比如使用memcheck工具去監(jiān)測內存,基本上是10到30倍的降速,明顯的降速會導致我們的軟件在業(yè)務上出現不可用的情況。關于降速,Valgrind官網上有著詳細的說明:
The main one is that programs run significantly more slowly under Valgrind. Depending on which tool you use, the slowdown factor can range from 5-100. Memcheck runs programs about 10-30x slower than normal.
? ? ? ?而Google提供的內存檢測工具AddressSanitizer在內存占用和運行速度方面有著卓越的表現,相比于Valgrind,AddressSanitizer的優(yōu)勢相當明顯。AddressSanitizer采用了一種取巧的影子內存玩法,將虛擬地址空間的1/8分配給它的影子內存,并使用一個帶有比例和偏移量的直接映射將一個應用程序地址轉換為它相應的影子地址,確保了少量內存就能完成一個程序的監(jiān)測。并且AddressSanitizer降速也比較少。AddressSanitizer在內存占用和降速方面,通過USENIX高等計算機系統(tǒng)協會某篇論文中的一段描述可以佐證:
We present AddressSanitizer, a new tool that combines performance and coverage. AddressSanitizer finds out-of-bounds accesses (for heap, stack, and global objects) and uses of freed heap memory at the relatively low cost of 73% slowdown,1.5x-4x memory overhead,making it a good choice for testing a wide range of C/C++ applications.
4.3、在很多實際項目中我們需要使用AddressSanitizer
? ? ? ?Valgrind采用的是二進制完全映射的影子內存技術,會占用更多內存才能去有效地監(jiān)測內存變化,還會明顯地拖慢程序的運行速度,可能會導致程序在收到請求后不能及時的響應,沒法模擬出真實運行時的場景,可能就不一定能復現問題,甚至還會因為運行速度過慢導致程序根本無法正常的運轉。所以我們有時需要使用占用內存少、運行速度更快的AddressSanitizer。
5、無法使用Valgrind的具體項目實例
?? ? ? ?在實際項目中我們遇到過不少無法使用Valgrind的場景。如果沒有內存檢測工具,排查起來效率非常低,僅僅通過打印日志和走讀代碼很難定位問題。如果有內存檢測工具,可能很快就能定位出來。所以后來轉向使用AddressSanitizer,很多Valgrind無法工作的場景,AddressSanitizer都可以勝任。與Valgrind相比,AddressSanitizer的運行速度是真的快,同時內存錯誤的檢測能力也非常強。
5.1、使用Valgrind檢測導致CPU占滿,無法進行檢測
? ? ? ?某客戶現場我們的程序出現了內存異常問題,最先使用Valgrind進行檢測,發(fā)現使用Valgrind檢測時機器的CPU一直是100%,直接導致程序業(yè)務無法正常工作,由于業(yè)務無法運轉,導致我們沒有辦法讓程序跑到存在問題的流程,所以檢測也就無法實施了。
5.2、使用Valgrind檢測導致程序運行過慢,無法進行檢測
? ? ? ?某客戶現場出現了多線程死鎖,使用gdb附加到目標程序上調試運行,發(fā)現某個公用模塊每次都會檢測到“它管理的堆的魔數被破壞”,于是強制退出了。由于該公用模塊中內存管理器使用的地方比較多,包括上層業(yè)務代碼和底層庫,通過走讀代碼去分析哪些地方分配了堆內存很難實施。于是我們使用Valgrind分析,但因為速度過慢程序沒法運轉起來,內存檢測任務無法實施。
6、AddressSanitizer與其他內存工具的比較
? ? ? ?AddressSanitizer與其他內存檢測工具的比較如下所示:
Items \ Tools | AddressSanitizer | Valgrind/Memcheck | Dr. Memory | Mudflap | Guard Page | gperftools |
technology | CTI | DBI | DBI | CTI | Library | Library |
ARCH | x86,ARM,PPC,... | x86,ARM,PPC | x86 | all(?) | all(?) | all(?) |
OS | Linux, Mac, Windows, FreeBSD, Android | Linux, Mac | Windows, Linux | Linux, Mac(?) | All (1) | Linux, Windows |
Slowdown | 2x | 20x | 10x | 2x-40x | ? | ? |
Detects: | ||||||
Heap OOB | yes | yes | yes | yes | some | some |
Stack OOB | yes | no | no | some | no | no |
Global OOB | yes | no | no | ? | no | no |
UAF | yes | yes | yes | yes | yes | yes |
UAR | yes?(see?UseAfterReturn) | no | no | no | no | no |
UMR | no (see?MemorySanitizer) | yes | yes | ? | no | no |
Leaks | yes?(see?LeakSanitizer) | yes | yes | ? | no | yes |
上表中的相關名詞說明如下:
DBI: dynamic binary instrumentation?
CTI: compile-time instrumentation?
UMR: uninitialized memory reads?
UAF: use-after-free (aka dangling pointer)?
UAR: use-after-return?
OOB: out-of-bounds?
x86: includes 32- and 64-bit.?
Guard Page: a family of memory error detectors (Electric fence?or?DUMA?on Linux, Page Heap on Windows, Guard Malloc in Mac)?gperftools: various performance tools/error detectors bundled with TCMalloc.?Heap checker?(leak detector) is only available on Linux.?Debug allocator?provides both guard pages and canary values for more precise detection of OOB writes, so it's better than guard page-only detectors.
7、如何使用AddressSanitizer?
?? ? ? ? 從gcc 4.8開始,gcc才集成AddressSanitizer工具,所以要使用AddressSanitizer必須將gcc升級到4.8或以上版本。然后使用高版本gcc對代碼進行重新編譯,在編譯時指定編譯選項就可以了。
7.1、升級gcc版本
? ? ? ?可以到ftp://gcc.gnu.org/pub/gcc上下載高版本的gcc,然后到執(zhí)行源碼樹中的contrib/download_prerequisites文件,它會下載和設置GCC編譯依賴的組件。然后在GCC源碼樹同級的目錄建立一個編譯目錄,比如叫build_dir,然后在該編譯目錄中執(zhí)行如下命令進行編譯和安裝:
../src_dir/configure
make
make install
7.2、如何配置使用AddressSanitizer進行內存檢測
? ? ? ? AddressSanitizer是內置在gcc中的,主要設置編譯參數去設定是否啟用AddressSanitizer的內存檢測。
1)如果沒使用makefile,直接gcc命令去編譯,則在命令中添加-fsanitize=address選項,如下:
gcc? -fsanitize=address? -fno-omit-frame-pointer? -O1? -g? use-after-free.c? -o? use-after-free
其中:
1)用-fsanitize=address選項編譯和鏈接你的程序。
2)用-fno-omit-frame-pointer編譯,以得到更容易理解stack trace。
3)可選擇-O1或者更高的優(yōu)化級別編譯
2)如果使用makefile,則在編譯選項CFLAGS和鏈接選項LDFLAGS中都要添加-fsanitize=address選項,如下:
#都要追加-fsanitize=address開關
CFLAGS+=-fsanitize=address?
LDFLAGS+=-fsanitize=address
7.3、使用AddressSanitizer進行內存檢測的實例
? ? ? ? ?比如下面的代碼中,分配array數組并釋放,然后返回它的一個元素,返回了一個已經釋放了的內存地址:
int main (int argc, char** argv)
{int* array = new int[100];delete []array;return array[1];
}
上述代碼放置在use-after-free.c中,直接使用gcc編譯該文件即可,命令如下:
gcc? -fsanitize=address? -fno-omit-frame-pointer? -O1? -g? use-after-free.c? -o? use-after-free
然后,運行use-after-fee,AddressSanitizer檢測了錯誤,就會打印出下面的信息:
==3189==ERROR: AddressSanitizer: heap-use-after-free on address 0x61400000fe44?
at pc 0x0000004008f1 bp 0x7ffc9b6e2630 sp 0x7ffc9b6e2620
READ of size 4 at 0x61400000fe44 thread T0#0 0x4008f0 in main /home/ron/dev/as/use_after_free.cpp:9#1 0x7f3763aa882f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)#2 0x4007b8 in _start (/home/ron/dev/as/build/use_after_free+0x4007b8)0x61400000fe44 is located 4 bytes inside of 400-byte region [0x61400000fe40,0x61400000ffd0)
freed by thread T0 here:#0 0x7f3763ef1caa in operator delete[](void*) (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x99caa)#1 0x4008b5 in main /home/ron/dev/as/use_after_free.cpp:8#2 0x7f3763aa882f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)previously allocated by thread T0 here:#0 0x7f3763ef16b2 in operator new[](unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x996b2)#1 0x40089e in main /home/ron/dev/as/use_after_free.cpp:7#2 0x7f3763aa882f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)SUMMARY: AddressSanitizer: heap-use-after-free /home/ron/dev/as/use_after_free.cpp:9 main
如上圖,打印出的信息主要分三部分:
1)ERROR部分:指出錯誤類型是heap-use-after-free;
2)READ部分:指出線程名thread T0,操作為READ,發(fā)生的位置是use-after-free.c:9(行號)。
該heapk塊之前已經在use-after-free.c:8(行號)被釋放了;
該heap塊是在use-fater-free.c:7(行號)分配的。
3)SUMMARY部分:前面輸出的概要說明。
7.4、使用AddressSanitizer的注意事項
? ? ? ?使用AddressSanitizer過程中可能會遇到一些問題,此處給大家講幾個注意事項。
1) 如果存在第三方內存管理器,可能需要取消對第三方管理器的依賴
? ? ? ?如果存在第三方內存管理器(比如tcmalloc),需要去掉第三方內存管理器的編譯選項或連接選項,因為內存管理器分配的內存自身會預留一些管理空間,越界不多只寫到這部分空間時,AddressSanitizer越界檢測是不會認為它是異常的(因為它們仍然是用戶分配的范圍之內的,第三方內存管理器,與應用程序對于ASan沒有差異的)。同時內存管理器通常會延遲釋放內存,這也會影響檢測的及時性。此外,如果鏈接時提示ASan中的符號找不到時給程序顯示添加對libasan庫的連接(默認在/usr/local/lib目錄,找不時使用find命令找下)。
2)內存不足問題
? ? ? ?內存檢測工具會增加程序的內存消耗,32位程序地址空間只有4G,用戶態(tài)的通常只有3G,如果程序跑起來之后提示無法分配內存,可以通過設置如下兩個選項緩解一下:
export ASAN_OPTIONS=quarantine_size_mb=256:start_deactivated=1。
其中:
quarantine_size_mb設置小點,犧牲使用已經釋放了的內存問題的檢測能力。
start_deactivated設置為1,啟動時不會加載asan的全部功能,用于節(jié)省內存。
上面的選項只是緩解,根本的解決之道還是要開發(fā)64位版本的程序。
3)錯誤忽略
? ? ? ? 有些錯誤我們改動不了或直接認為絕對安全,也可以在函數上面添加屬性,進行錯誤忽略。比如:
#if defined(__clang__) || defined (__GNUC__)
# define ATTRIBUTE_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address))
#else
# define ATTRIBUTE_NO_SANITIZE_ADDRESS
#endif
...
ATTRIBUTE_NO_SANITIZE_ADDRESS
void ThisFunctionWillNotBeInstrumented() {...}
8、Windows平臺高版本的Visual Studio也支持AddressSanitizer工具
? ? ? ?AddressSanitizer工具原先只支持Linux,現在也可以在Windows上使用了。微軟在Visual Studio 2019的16.9版本們引入了AddressSanitizer,在安裝Visual Studio 2019的16.9版本及以后的版本時,會默認安裝AddressSanitizer工具:(默認勾選“C++ AddressSanitizer”)
? ? ? ?對于如何在VS中如何使用AddressSanitizer內存分析工具,可以看一下微軟官方文章的詳細說明:
在Visual Studio中使用AddressSanitizerhttps://docs.microsoft.com/zh-cn/cpp/sanitizers/asan?view=msvc-170此處我就不詳細展開了,大家需要使用的話,可以去詳細研究一下。