AddressSanitizer(簡稱 ASAN)一直是一個檢測分析 C/C++ 內(nèi)存問題很方便的工具。WebRTC 工程集成了 ASAN,只要配置一個簡單的選項即可對整個工程打開或關(guān)閉 ASAN葱轩,具體來說是 is_asan
選項。is_asan
選項的默認(rèn)值為 false
藐握,在 args.gn
文件中寫入 is_asan = true
行可以對整個工程打開 ASAN靴拱,在 args.gn
文件中寫入 is_asan = false
行或者不配置 is_asan
選項可以對整個工程關(guān)閉 ASAN。
OpenRTCClient 工程的 Linux debug 構(gòu)建是開了 ASAN 的猾普。如果一切選項配置妥當(dāng)袜炕,執(zhí)行一個 C/C++ 應(yīng)用程序,在出現(xiàn)內(nèi)存問題時初家,ASAN 將調(diào)用 symbolizer 把出現(xiàn)內(nèi)存問題的相關(guān)堆棧(如內(nèi)存分配的堆棧和內(nèi)存釋放的內(nèi)存堆棧)的內(nèi)存地址轉(zhuǎn)為文件行號和符號名偎窘。我們可以配置環(huán)境變量 ASAN_SYMBOLIZER_PATH
指向我們選擇的 llvm symbolizer乌助,如 export ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-11
,來告訴 ASAN 在需要把內(nèi)存地址符號化時用什么工具陌知。不配置環(huán)境變量 ASAN_SYMBOLIZER_PATH
時眷茁,ASAN 會嘗試在 PATH
環(huán)境變量的各個路徑下尋找名為 llvm-symbolizer
的可執(zhí)行文件來用。如果既沒有配置 ASAN_SYMBOLIZER_PATH
指向合適的 llvm symbolizer纵诞,PATH
環(huán)境變量的各個路徑下也找不到名為 llvm-symbolizer
的可執(zhí)行文件上祈,則 ASAN 只能簡單地把內(nèi)存地址吐出來。
一次內(nèi)存地址符號化失敗
OpenRTCClient 工程中的示例應(yīng)用 loop_connect
浙芙,編譯完成登刺,在執(zhí)行之前配置了環(huán)境變量 ASAN_SYMBOLIZER_PATH
,在 loop_connect
執(zhí)行過程中嗡呼,出現(xiàn)內(nèi)存問題時纸俭,依然沒能成功將內(nèi)存地址符號化,ASAN 輸出如下:
=================================================================
==51148==ERROR: AddressSanitizer: heap-use-after-free on address 0x61200014eb40 at pc 0x5639128a0a85 bp 0x7ffcfdbb6b30 sp 0x7ffcfdbb6b28
READ of size 8 at 0x61200014eb40 thread T0
==51148==WARNING: invalid path to external symbolizer!
==51148==WARNING: Failed to use and restart external symbolizer!
#0 0x5639128a0a84 (~/OpenRTCClient/build/linux/x64/debug/loop_connect+0x32fda84) (BuildId: 542ad276a9f6ad54)
#1 0x563915cdc29d (~/OpenRTCClient/build/linux/x64/debug/loop_connect+0x673929d) (BuildId: 542ad276a9f6ad54)
#2 0x563910cd2bc1 (~/OpenRTCClient/build/linux/x64/debug/loop_connect+0x172fbc1) (BuildId: 542ad276a9f6ad54)
#3 0x563910cd2c08 (~/OpenRTCClient/build/linux/x64/debug/loop_connect+0x172fc08) (BuildId: 542ad276a9f6ad54)
#4 0x563910cd52f6 (~/OpenRTCClient/build/linux/x64/debug/loop_connect+0x17322f6) (BuildId: 542ad276a9f6ad54)
#5 0x563910cd3b40 (~/OpenRTCClient/build/linux/x64/debug/loop_connect+0x1730b40) (BuildId: 542ad276a9f6ad54)
#6 0x563910ccf40d (~/OpenRTCClient/build/linux/x64/debug/loop_connect+0x172c40d) (BuildId: 542ad276a9f6ad54)
#7 0x563910ccbad9 (~/OpenRTCClient/build/linux/x64/debug/loop_connect+0x1728ad9) (BuildId: 542ad276a9f6ad54)
#8 0x7efd969cc0b2 (/lib/x86_64-linux-gnu/libc.so.6+0x240b2) (BuildId: 9fdb74e7b217d06c93172a8243f8547f947ee6d1)
0x61200014eb40 is located 0 bytes inside of 320-byte region [0x61200014eb40,0x61200014ec80)
freed by thread T0 here:
#0 0x563910ca3887 (~/OpenRTCClient/build/linux/x64/debug/loop_connect+0x1700887) (BuildId: 542ad276a9f6ad54)
#1 0x5639122c1791 (~/OpenRTCClient/build/linux/x64/debug/loop_connect+0x2d1e791) (BuildId: 542ad276a9f6ad54)
#2 0x563910cbbc76 (~/OpenRTCClient/build/linux/x64/debug/loop_connect+0x1718c76) (BuildId: 542ad276a9f6ad54)
#3 0x563910cbbb1f (~/OpenRTCClient/build/linux/x64/debug/loop_connect+0x1718b1f) (BuildId: 542ad276a9f6ad54)
#4 0x563910cbdbfa (~/OpenRTCClient/build/linux/x64/debug/loop_connect+0x171abfa) (BuildId: 542ad276a9f6ad54)
#5 0x563910cb74c0 (~/OpenRTCClient/build/linux/x64/debug/loop_connect+0x17144c0) (BuildId: 542ad276a9f6ad54)
#6 0x563910cb1384 (~/OpenRTCClient/build/linux/x64/debug/loop_connect+0x170e384) (BuildId: 542ad276a9f6ad54)
#7 0x563910ccd4c4 (~/OpenRTCClient/build/linux/x64/debug/loop_connect+0x172a4c4) (BuildId: 542ad276a9f6ad54)
#8 0x563910ccd42c (~/OpenRTCClient/build/linux/x64/debug/loop_connect+0x172a42c) (BuildId: 542ad276a9f6ad54)
#9 0x563910ccd105 (~/OpenRTCClient/build/linux/x64/debug/loop_connect+0x172a105) (BuildId: 542ad276a9f6ad54)
#10 0x563910cbc8ee (~/OpenRTCClient/build/linux/x64/debug/loop_connect+0x17198ee) (BuildId: 542ad276a9f6ad54)
#11 0x563910cbc6e5 (~/OpenRTCClient/build/linux/x64/debug/loop_connect+0x17196e5) (BuildId: 542ad276a9f6ad54)
#12 0x563910ccd858 (~/OpenRTCClient/build/linux/x64/debug/loop_connect+0x172a858) (BuildId: 542ad276a9f6ad54)
#13 0x563910ccbc84 (~/OpenRTCClient/build/linux/x64/debug/loop_connect+0x1728c84) (BuildId: 542ad276a9f6ad54)
#14 0x563910ccad26 (~/OpenRTCClient/build/linux/x64/debug/loop_connect+0x1727d26) (BuildId: 542ad276a9f6ad54)
ASAN 提示說南窗,拿到的 llvm symbolizer 地址無效揍很,內(nèi)存地址符號化失敗。
ASAN 的實現(xiàn)
AddressSanitizer 是 LLVM 工程的 compiler-rt
子工程的一部分万伤。在 GitHub 下載 llvm-project 工程的代碼窒悔,compiler-rt
的代碼就位于 llvm-project/compiler-rt
目錄下。一般來說敌买,我們需要構(gòu)建 LLVM/Clang 來構(gòu)建 compiler-rt
简珠。我們可以把 compiler-rt
和 llvm 及 clang 放在一起構(gòu)建,但我們也可以分開來構(gòu)建虹钮。
要把 compiler-rt
和 llvm 及 clang 放在一起構(gòu)建聋庵,則把 compiler-rt
添加到傳給 cmake 的 -DLLVM_ENABLE_RUNTIMES=
選項即可。
要分開構(gòu)建芙粱,則首先單獨(dú) 構(gòu)建 LLVM 以獲得 llvm-config
二進(jìn)制可執(zhí)行文件祭玉,然后運(yùn)行如下命令:
$ cd llvm-project
$ git checkout -t origin/release/14.x
$ mkdir build-compiler-rt
$ cd build-compiler-rt
$ cmake ../compiler-rt -DLLVM_CONFIG_PATH=/path/to/llvm-config
$ make
(OpenRTCClient 工程所基于的 WebRTC 代碼庫中的 llvm 已經(jīng)更新到了 llvm-14,因而這里也切到 llvm-14 的分支來構(gòu)建春畔。)
編譯生成的二進(jìn)制庫文件主要位于 llvm-project/build-compiler-rt/lib/linux/
脱货,如:
llvm-project/build-compiler-rt$ ls lib/linux/
clang_rt.crtbegin-x86_64.o libclang_rt.hwasan_aliases-x86_64.so libclang_rt.scudo-x86_64.a
clang_rt.crtend-x86_64.o libclang_rt.hwasan_cxx-x86_64.a libclang_rt.scudo-x86_64.so
libclang_rt.asan_cxx-x86_64.a libclang_rt.hwasan_cxx-x86_64.a.syms libclang_rt.tsan_cxx-x86_64.a
libclang_rt.asan_cxx-x86_64.a.syms libclang_rt.hwasan-x86_64.a libclang_rt.tsan_cxx-x86_64.a.syms
libclang_rt.asan-preinit-x86_64.a libclang_rt.hwasan-x86_64.a.syms libclang_rt.tsan-x86_64.a
libclang_rt.asan_static-x86_64.a libclang_rt.hwasan-x86_64.so libclang_rt.tsan-x86_64.a.syms
libclang_rt.asan-x86_64.a libclang_rt.lsan-x86_64.a libclang_rt.tsan-x86_64.so
libclang_rt.asan-x86_64.a.syms libclang_rt.msan_cxx-x86_64.a libclang_rt.ubsan_minimal-x86_64.a
libclang_rt.asan-x86_64.so libclang_rt.msan_cxx-x86_64.a.syms libclang_rt.ubsan_minimal-x86_64.a.syms
libclang_rt.builtins-x86_64.a libclang_rt.msan-x86_64.a libclang_rt.ubsan_minimal-x86_64.so
libclang_rt.cfi_diag-x86_64.a libclang_rt.msan-x86_64.a.syms libclang_rt.ubsan_standalone_cxx-x86_64.a
libclang_rt.cfi-x86_64.a libclang_rt.orc-x86_64.a libclang_rt.ubsan_standalone_cxx-x86_64.a.syms
libclang_rt.dd-x86_64.a libclang_rt.profile-x86_64.a libclang_rt.ubsan_standalone-x86_64.a
libclang_rt.dfsan-x86_64.a libclang_rt.safestack-x86_64.a libclang_rt.ubsan_standalone-x86_64.a.syms
libclang_rt.dfsan-x86_64.a.syms libclang_rt.scudo_cxx_minimal-x86_64.a libclang_rt.ubsan_standalone-x86_64.so
libclang_rt.dyndd-x86_64.so libclang_rt.scudo_cxx-x86_64.a libclang_rt.xray-basic-x86_64.a
libclang_rt.gwp_asan-x86_64.a libclang_rt.scudo_minimal-x86_64.a libclang_rt.xray-fdr-x86_64.a
libclang_rt.hwasan_aliases_cxx-x86_64.a libclang_rt.scudo_minimal-x86_64.so libclang_rt.xray-profiling-x86_64.a
libclang_rt.hwasan_aliases_cxx-x86_64.a.syms libclang_rt.scudo_standalone_cxx-x86_64.a libclang_rt.xray-x86_64.a
libclang_rt.hwasan_aliases-x86_64.a libclang_rt.scudo_standalone-x86_64.a
libclang_rt.hwasan_aliases-x86_64.a.syms libclang_rt.scudo_standalone-x86_64.so
開啟 AddressSanitizer 在編譯器/鏈接器層面,是給編譯器和鏈接器加上特殊的參數(shù) -fsanitize=address
拐迁,如鏈接 OpenRTCClient 的示例應(yīng)用 loop_connect
實際執(zhí)行的命令如下:
python3 "../../../../webrtc/build/toolchain/gcc_link_wrapper.py" --output="./loop_connect" -- ../../../../build_system/llvm-build/linux/linux/Release+Asserts/bin/clang++ -fuse-ld=lld -Wl,--fatal-warnings -Wl,--build-id -fPIC -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -Wl,--color-diagnostics -Wl,--no-call-graph-profile-sort -m64 -no-canonical-prefixes -Wl,--gdb-index -rdynamic --sysroot=../../../../build_system/sysroot/linux/debian_sid_amd64-sysroot -fsanitize=address -pie -Wl,--disable-new-dtags -Wl,-u_sanitizer_options_link_helper -fsanitize=address -o "./loop_connect" -Wl,--start-group @"./loop_connect.rsp" -Wl,--end-group -lX11 -lXcomposite -lXext -lXrender -latomic -ldl -lpthread -lrt -lgmodule-2.0 -lgthread-2.0 -lgtk-3 -lgdk-3 -lpangocairo-1.0 -lpango-1.0 -lharfbuzz -latk-1.0 -lcairo-gobject -lcairo -lgdk_pixbuf-2.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0 -lm -lz
鏈接器在看到 -fsanitize=address
參數(shù)時蹭劈,會根據(jù)編譯的目標(biāo)架構(gòu),去鏈接前面看到的 compiler-rt
編譯出來的某個 libclang_rt.asan*
庫线召。對于 OpenRTCClient 的示例應(yīng)用 loop_connect
來說铺韧,鏈接可執(zhí)行文件時,傳入了 --sysroot
參數(shù)缓淹,這樣就會在 --sysroot
參數(shù)指定的路徑下查找編譯鏈接時需要的所有庫文件和頭文件等哈打。具體來說塔逃,鏈接 loop_connect
時將鏈接到 OpenRTCClient/build_system/llvm-build/linux/linux/Release+Asserts/lib/clang/14.0.0/lib/linux
或 OpenRTCClient/build_system/llvm-build/linux/linux/Release+Asserts/lib/clang/14.0.0/lib/x86_64-unknown-linux-gnu
目錄下對應(yīng)于目標(biāo)架構(gòu)的 libclang_rt.asan*
庫文件。
為了能夠調(diào)試 AddressSanitizer料仗,我們需要讓鏈接器去鏈接我們編譯出來的 compiler-rt
庫湾盗。具體做法是,把 OpenRTCClient/build_system/llvm-build/linux/linux/Release+Asserts/lib/clang/14.0.0/lib/linux
和 OpenRTCClient/build_system/llvm-build/linux/linux/Release+Asserts/lib/clang/14.0.0/lib/x86_64-unknown-linux-gnu
隨意改個其它名字立轧,同時在 OpenRTCClient/build_system/llvm-build/linux/linux/Release+Asserts/lib/clang/14.0.0/lib/
目錄下創(chuàng)建一個名為 linux
的符號鏈接指向我們編譯 compiler-rt
的目錄 llvm-project/build-compiler-rt/lib/linux
格粪,這樣我們修改 compiler-rt
的代碼,編譯 compiler-rt
氛改,然后鏈接 loop_connect
帐萎,會將我們修改過的 compiler-rt
代碼鏈接進(jìn)去。
AddressSanitizer 找不到 symbolizer 問題分析
尋著 AddressSanitizer 給出來的提示信息胜卤,在 compiler-rt
的代碼中搜常量字符串 "WARNING: invalid path to external symbolizer!"
疆导,我們可以發(fā)現(xiàn),它位于 llvm-project/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_posix_libcdep.cpp
葛躏,相關(guān)的代碼如下:
bool SymbolizerProcess::StartSymbolizerSubprocess() {
if (!FileExists(path_)) {
if (!reported_invalid_path_) {
Report("WARNING: invalid path to external symbolizer!\n");
reported_invalid_path_ = true;
}
return false;
}
const char *argv[kArgVMax];
GetArgV(path_, argv);
pid_t pid;
我們可以修改這里的代碼澈段,來查下 AddressSanitizer 在這里看到的 symbolizer 的地址 path_
具體是什么〗⒃埽可以看到败富,這里的 symbolizer 的地址 path_
具體是 /media/data/multimedia/OpenRTCClient/build/linux/x64/debug//../../third_party/llvm-build/Release+Asserts/bin/llvm-symbolizer
。這個值貌似跟我們通過環(huán)境變量 ASAN_SYMBOLIZER_PATH
配置的地址完全沒有關(guān)系芒率。
path_
的值是在 SymbolizerProcess
類的構(gòu)造函數(shù)中傳入的囤耳,具體的代碼如下:
SymbolizerProcess::SymbolizerProcess(const char *path, bool use_posix_spawn)
: path_(path),
input_fd_(kInvalidFd),
output_fd_(kInvalidFd),
times_restarted_(0),
failed_to_start_(false),
reported_invalid_path_(false),
use_posix_spawn_(use_posix_spawn) {
CHECK(path_);
CHECK_NE(path_[0], '\0');
}
把我們的可執(zhí)行文件丟進(jìn) GDB 執(zhí)行,在 SymbolizerProcess
類的構(gòu)造函數(shù)這里加個斷點(diǎn)偶芍,可以看到如下這樣的調(diào)用堆棧:
#0 __sanitizer::SymbolizerProcess::SymbolizerProcess(char const*, bool)
(use_posix_spawn=false, path=0x7ffff3403000 "/media/data/multimedia/OpenRTCClient/build/linux/x64/debug//../../third_party/llvm-build/Release+Asserts/bin/llvm-symbolizer", this=0x7ffff7fab000) at ~llvm-project/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_libcdep.cpp:456
#1 __sanitizer::LLVMSymbolizerProcess::LLVMSymbolizerProcess(char const*)
(path=0x7ffff3403000 "/media/data/multimedia/OpenRTCClient/build/linux/x64/debug//../../third_party/llvm-build/Release+Asserts/bin/llvm-symbolizer", this=0x7ffff7fab000) at ~llvm-project/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_libcdep.cpp:240
#2 __sanitizer::LLVMSymbolizer::LLVMSymbolizer(char const*, __sanitizer::LowLevelAllocator*)
(this=0x7ffff7fb4000, path=0x7ffff3403000 "/media/data/multimedia/OpenRTCClient/build/linux/x64/debug//../../third_party/llvm-build/Release+Asserts/bin/llvm-symbolizer", allocator=<optimized out>) at ~llvm-project/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_libcdep.cpp:292
#3 0x0000555556c45532 in __sanitizer::ChooseExternalSymbolizer (allocator=<optimized out>)
at ~llvm-project/compiler-rt/lib/sanitizer_common/sanitizer_common.h:1075
#4 __sanitizer::ChooseSymbolizerTools (allocator=<optimized out>, list=<synthetic pointer>)
at ~llvm-project/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_posix_libcdep.cpp:487
#5 __sanitizer::Symbolizer::PlatformInit() ()
at ~llvm-project/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_posix_libcdep.cpp:500
#6 0x0000555556c42455 in __sanitizer::Symbolizer::GetOrInit() ()
at ~llvm-project/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_libcdep.cpp:24
#7 0x0000555556c457ad in __sanitizer::Symbolizer::LateInitialize() ()
at ~llvm-project/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_posix_libcdep.cpp:505
#8 0x0000555556c199fd in __asan::AsanInitInternal() () at ~llvm-project/compiler-rt/lib/asan/asan_rtl.cpp:495
#9 0x00007ffff7fe0ce6 in () at /lib64/ld-linux-x86-64.so.2
#10 0x00007ffff7fd013a in () at /lib64/ld-linux-x86-64.so.2
#11 0x0000000000000001 in ()
在 llvm-project/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_posix_libcdep.cpp
文件中定義的 __sanitizer::ChooseExternalSymbolizer ()
函數(shù),我們可以看到 SymbolizerProcess
對象的 path_
的來源:
static SymbolizerTool *ChooseExternalSymbolizer(LowLevelAllocator *allocator) {
const char *path = common_flags()->external_symbolizer_path;
if (path && internal_strchr(path, '%')) {
char *new_path = (char *)InternalAlloc(kMaxPathLength);
SubstituteForFlagValue(path, new_path, kMaxPathLength);
path = new_path;
}
const char *binary_name = path ? StripModuleName(path) : "";
static const char kLLVMSymbolizerPrefix[] = "llvm-symbolizer";
if (path && path[0] == '\0') {
VReport(2, "External symbolizer is explicitly disabled.\n");
return nullptr;
} else if (!internal_strncmp(binary_name, kLLVMSymbolizerPrefix,
internal_strlen(kLLVMSymbolizerPrefix))) {
VReport(2, "Using llvm-symbolizer at user-specified path: %s\n", path);
return new(*allocator) LLVMSymbolizer(path, allocator);
} else if (!internal_strcmp(binary_name, "atos")) {
#if SANITIZER_MAC
VReport(2, "Using atos at user-specified path: %s\n", path);
return new(*allocator) AtosSymbolizer(path, allocator);
#else // SANITIZER_MAC
Report("ERROR: Using `atos` is only supported on Darwin.\n");
Die();
#endif // SANITIZER_MAC
} else if (!internal_strcmp(binary_name, "addr2line")) {
VReport(2, "Using addr2line at user-specified path: %s\n", path);
return new(*allocator) Addr2LinePool(path, allocator);
} else if (path) {
Report("ERROR: External symbolizer path is set to '%s' which isn't "
"a known symbolizer. Please set the path to the llvm-symbolizer "
"binary or other known tool.\n", path);
Die();
}
// Otherwise symbolizer program is unknown, let's search $PATH
CHECK(path == nullptr);
#if SANITIZER_MAC
if (const char *found_path = FindPathToBinary("atos")) {
VReport(2, "Using atos found at: %s\n", found_path);
return new(*allocator) AtosSymbolizer(found_path, allocator);
}
#endif // SANITIZER_MAC
if (const char *found_path = FindPathToBinary("llvm-symbolizer")) {
VReport(2, "Using llvm-symbolizer found at: %s\n", found_path);
return new(*allocator) LLVMSymbolizer(found_path, allocator);
}
if (common_flags()->allow_addr2line) {
if (const char *found_path = FindPathToBinary("addr2line")) {
VReport(2, "Using addr2line found at: %s\n", found_path);
return new(*allocator) Addr2LinePool(found_path, allocator);
}
}
return nullptr;
}
在 __sanitizer::ChooseExternalSymbolizer ()
這個函數(shù)里德玫,AddressSanitizer 會嘗試根據(jù) common_flags()->external_symbolizer_path
等值確定 symbolizer 程序的路徑匪蟀。我們可以看到,這里的 common_flags()->external_symbolizer_path
的實際值為 %d/../../third_party/llvm-build/Release+Asserts/bin/llvm-symbolizer
宰僧,上面看到的 SymbolizerProcess
對象的 path_
即是根據(jù)這個值算出來的材彪。
在 llvm-project/compiler-rt/lib/sanitizer_common/sanitizer_flags.h
文件中,common_flags()
函數(shù)的定義為:
// Functions to get/set global CommonFlags shared by all sanitizer runtimes:
extern CommonFlags common_flags_dont_use;
inline const CommonFlags *common_flags() {
return &common_flags_dont_use;
}
inline void SetCommonFlagsDefaults() {
common_flags_dont_use.SetDefaults();
}
// This function can only be used to setup tool-specific overrides for
// CommonFlags defaults. Generally, it should only be used right after
// SetCommonFlagsDefaults(), but before ParseCommonFlagsFromString(), and
// only during the flags initialization (i.e. before they are used for
// the first time).
inline void OverrideCommonFlags(const CommonFlags &cf) {
common_flags_dont_use.CopyFrom(cf);
}
即 common_flags()
函數(shù)返回的是一個全局對象琴儿。這個全局對象的值段化,主要由 llvm-project/compiler-rt/lib/asan/asan_flags.cpp
文件中的 InitializeFlags()
函數(shù)來更新,這個函數(shù)的定義如下:
void InitializeFlags() {
// Set the default values and prepare for parsing ASan and common flags.
SetCommonFlagsDefaults();
{
CommonFlags cf;
cf.CopyFrom(*common_flags());
cf.detect_leaks = cf.detect_leaks && CAN_SANITIZE_LEAKS;
cf.external_symbolizer_path = GetEnv("ASAN_SYMBOLIZER_PATH");
cf.malloc_context_size = kDefaultMallocContextSize;
cf.intercept_tls_get_addr = true;
cf.exitcode = 1;
OverrideCommonFlags(cf);
}
Flags *f = flags();
f->SetDefaults();
FlagParser asan_parser;
RegisterAsanFlags(&asan_parser, f);
RegisterCommonFlags(&asan_parser);
// Set the default values and prepare for parsing LSan and UBSan flags
// (which can also overwrite common flags).
#if CAN_SANITIZE_LEAKS
__lsan::Flags *lf = __lsan::flags();
lf->SetDefaults();
FlagParser lsan_parser;
__lsan::RegisterLsanFlags(&lsan_parser, lf);
RegisterCommonFlags(&lsan_parser);
#endif
#if CAN_SANITIZE_UB
__ubsan::Flags *uf = __ubsan::flags();
uf->SetDefaults();
FlagParser ubsan_parser;
__ubsan::RegisterUbsanFlags(&ubsan_parser, uf);
RegisterCommonFlags(&ubsan_parser);
#endif
if (SANITIZER_MAC) {
// Support macOS MallocScribble and MallocPreScribble:
// <https://developer.apple.com/library/content/documentation/Performance/
// Conceptual/ManagingMemory/Articles/MallocDebug.html>
if (GetEnv("MallocScribble")) {
f->max_free_fill_size = 0x1000;
}
if (GetEnv("MallocPreScribble")) {
f->malloc_fill_byte = 0xaa;
}
}
// Override from ASan compile definition.
const char *asan_compile_def = MaybeUseAsanDefaultOptionsCompileDefinition();
asan_parser.ParseString(asan_compile_def);
// Override from user-specified string.
const char *asan_default_options = __asan_default_options();
asan_parser.ParseString(asan_default_options);
#if CAN_SANITIZE_UB
const char *ubsan_default_options = __ubsan_default_options();
ubsan_parser.ParseString(ubsan_default_options);
#endif
#if CAN_SANITIZE_LEAKS
const char *lsan_default_options = __lsan_default_options();
lsan_parser.ParseString(lsan_default_options);
#endif
// Override from command line.
asan_parser.ParseStringFromEnv("ASAN_OPTIONS");
#if CAN_SANITIZE_LEAKS
lsan_parser.ParseStringFromEnv("LSAN_OPTIONS");
#endif
#if CAN_SANITIZE_UB
ubsan_parser.ParseStringFromEnv("UBSAN_OPTIONS");
#endif
InitializeCommonFlags();
// TODO(eugenis): dump all flags at verbosity>=2?
if (Verbosity()) ReportUnrecognizedFlags();
if (common_flags()->help) {
// TODO(samsonov): print all of the flags (ASan, LSan, common).
asan_parser.PrintFlagDescriptions();
}
// Flag validation:
if (!CAN_SANITIZE_LEAKS && common_flags()->detect_leaks) {
Report("%s: detect_leaks is not supported on this platform.\n",
SanitizerToolName);
Die();
}
// Ensure that redzone is at least ASAN_SHADOW_GRANULARITY.
if (f->redzone < (int)ASAN_SHADOW_GRANULARITY)
f->redzone = ASAN_SHADOW_GRANULARITY;
// Make "strict_init_order" imply "check_initialization_order".
// TODO(samsonov): Use a single runtime flag for an init-order checker.
if (f->strict_init_order) {
f->check_initialization_order = true;
}
CHECK_LE((uptr)common_flags()->malloc_context_size, kStackTraceMax);
CHECK_LE(f->min_uar_stack_size_log, f->max_uar_stack_size_log);
CHECK_GE(f->redzone, 16);
CHECK_GE(f->max_redzone, f->redzone);
CHECK_LE(f->max_redzone, 2048);
CHECK(IsPowerOfTwo(f->redzone));
CHECK(IsPowerOfTwo(f->max_redzone));
// quarantine_size is deprecated but we still honor it.
// quarantine_size can not be used together with quarantine_size_mb.
if (f->quarantine_size >= 0 && f->quarantine_size_mb >= 0) {
Report("%s: please use either 'quarantine_size' (deprecated) or "
"quarantine_size_mb, but not both\n", SanitizerToolName);
Die();
}
if (f->quarantine_size >= 0)
f->quarantine_size_mb = f->quarantine_size >> 20;
if (f->quarantine_size_mb < 0) {
const int kDefaultQuarantineSizeMb =
(ASAN_LOW_MEMORY) ? 1UL << 4 : 1UL << 8;
f->quarantine_size_mb = kDefaultQuarantineSizeMb;
}
if (f->thread_local_quarantine_size_kb < 0) {
const u32 kDefaultThreadLocalQuarantineSizeKb =
// It is not advised to go lower than 64Kb, otherwise quarantine batches
// pushed from thread local quarantine to global one will create too
// much overhead. One quarantine batch size is 8Kb and it holds up to
// 1021 chunk, which amounts to 1/8 memory overhead per batch when
// thread local quarantine is set to 64Kb.
(ASAN_LOW_MEMORY) ? 1 << 6 : FIRST_32_SECOND_64(1 << 8, 1 << 10);
f->thread_local_quarantine_size_kb = kDefaultThreadLocalQuarantineSizeKb;
}
if (f->thread_local_quarantine_size_kb == 0 && f->quarantine_size_mb > 0) {
Report("%s: thread_local_quarantine_size_kb can be set to 0 only when "
"quarantine_size_mb is set to 0\n", SanitizerToolName);
Die();
}
if (!f->replace_str && common_flags()->intercept_strlen) {
Report("WARNING: strlen interceptor is enabled even though replace_str=0. "
"Use intercept_strlen=0 to disable it.");
}
if (!f->replace_str && common_flags()->intercept_strchr) {
Report("WARNING: strchr* interceptors are enabled even though "
"replace_str=0. Use intercept_strchr=0 to disable them.");
}
if (!f->replace_str && common_flags()->intercept_strndup) {
Report("WARNING: strndup* interceptors are enabled even though "
"replace_str=0. Use intercept_strndup=0 to disable them.");
}
}
在 InitializeFlags()
函數(shù)中造成,首先會給 CommonFlags common_flags_dont_use
設(shè)置默認(rèn)值显熏,隨后會從環(huán)境變量里獲取一些值來更新,即我們配置的環(huán)境變量 ASAN_SYMBOLIZER_PATH
晒屎,之后依次根據(jù)從 MaybeUseAsanDefaultOptionsCompileDefinition()
喘蟆、__asan_default_options()
等函數(shù)中缓升,以及從 ASAN_OPTIONS
等環(huán)境變量中獲取選項,來覆蓋前面的設(shè)置蕴轨。
在這里港谊,我們打印從環(huán)境變量 ASAN_SYMBOLIZER_PATH
獲取的值,發(fā)現(xiàn)它就是我們配置的值 /usr/bin/llvm-symbolizer-11
橙弱。llvm-project/compiler-rt/lib/asan/asan_flags.cpp
文件中 MaybeUseAsanDefaultOptionsCompileDefinition()
函數(shù)的定義如下:
static const char *MaybeUseAsanDefaultOptionsCompileDefinition() {
#ifdef ASAN_DEFAULT_OPTIONS
return SANITIZER_STRINGIFY(ASAN_DEFAULT_OPTIONS);
#else
return "";
#endif
}
llvm-project/compiler-rt/lib/asan/asan_flags.cpp
文件中 __asan_default_options()
函數(shù)的定義如下:
SANITIZER_INTERFACE_WEAK_DEF(const char*, __asan_default_options, void) {
return "";
}
直觀地看歧寺,這兩個函數(shù)返回的配置選項不會更新 common_flags()->external_symbolizer_path
。但實際上棘脐,經(jīng)過了對 __asan_default_options()
函數(shù)的返回值的處理之后斜筐,common_flags()->external_symbolizer_path
的值被更新為了 %d/../../third_party/llvm-build/Release+Asserts/bin/llvm-symbolizer
。且 __asan_default_options()
函數(shù)實際返回的字符串也不是上面我們看到的 __asan_default_options()
函數(shù)定義中的空字符串荆残,而是如下這個字符串:
check_printf=1 use_sigaltstack=1 strip_path_prefix=/../../ fast_unwind_on_fatal=1 detect_stack_use_after_return=1 symbolize=1 detect_leaks=0 allow_user_segv_handler=1 external_symbolizer_path=%d/../../third_party/llvm-build/Release+Asserts/bin/llvm-symbolizer
我們把我們的可執(zhí)行文件丟進(jìn) GDB
中跑奴艾,并給 __asan_default_options()
函數(shù)加個斷點(diǎn)。令我們驚訝的是内斯,斷點(diǎn)的位置并沒有被加在 llvm-project/compiler-rt/lib/asan/asan_flags.cpp
文件中蕴潦,而是加在了 WebRTC 的代碼中 webrtc/build/sanitizers/sanitizer_options.cc
:
(gdb) break __asan_default_options
warning: Could not find DWO CU obj/build/config/sanitizers/options_sources/sanitizer_options.dwo(0x4d51bcd290d078c0) referenced by CU at offset 0x43f087 [in module /media/data/multimedia/OpenRTCClient/build/linux/x64/debug/loop_connect]
Breakpoint 1 at 0x81c6e34: file ../../../../webrtc/build/sanitizers/sanitizer_options.cc, line 75.
再來審視一下 __asan_default_options()
函數(shù)的聲明和定義,發(fā)現(xiàn)在 llvm 中它被定義為了一個弱符號俘闯。而在 WebRTC 的代碼 webrtc/build/sanitizers/sanitizer_options.cc
中有 __asan_default_options()
函數(shù)的定義如下:
#if defined(ADDRESS_SANITIZER)
// Default options for AddressSanitizer in various configurations:
// check_printf=1 - check the memory accesses to printf (and other formatted
// output routines) arguments.
// use_sigaltstack=1 - handle signals on an alternate signal stack. Useful
// for stack overflow detection.
// strip_path_prefix=/../../ - prefixes up to and including this
// substring will be stripped from source file paths in symbolized reports
// fast_unwind_on_fatal=1 - use the fast (frame-pointer-based) stack unwinder
// to print error reports. V8 doesn't generate debug info for the JIT code,
// so the slow unwinder may not work properly.
// detect_stack_use_after_return=1 - use fake stack to delay the reuse of
// stack allocations and detect stack-use-after-return errors.
// symbolize=1 - enable in-process symbolization.
// external_symbolizer_path=... - provides the path to llvm-symbolizer
// relative to the main executable
#if defined(OS_LINUX) || defined(OS_CHROMEOS)
const char kAsanDefaultOptions[] =
"check_printf=1 use_sigaltstack=1 strip_path_prefix=/../../ "
"fast_unwind_on_fatal=1 detect_stack_use_after_return=1 "
"symbolize=1 detect_leaks=0 allow_user_segv_handler=1 "
"external_symbolizer_path=%d/../../third_party/llvm-build/Release+Asserts/"
"bin/llvm-symbolizer";
#elif defined(OS_APPLE)
const char* kAsanDefaultOptions =
"check_printf=1 use_sigaltstack=1 strip_path_prefix=/../../ "
"fast_unwind_on_fatal=1 detect_stack_use_after_return=1 ";
#elif defined(OS_WIN)
const char* kAsanDefaultOptions =
"check_printf=1 use_sigaltstack=1 strip_path_prefix=\\..\\..\\ "
"fast_unwind_on_fatal=1 detect_stack_use_after_return=1 "
"symbolize=1 external_symbolizer_path=%d/../../third_party/"
"llvm-build/Release+Asserts/bin/llvm-symbolizer.exe";
#endif // defined(OS_LINUX) || defined(OS_CHROMEOS)
#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_APPLE) || \
defined(OS_WIN)
// Allow NaCl to override the default asan options.
extern const char* kAsanDefaultOptionsNaCl;
__attribute__((weak)) const char* kAsanDefaultOptionsNaCl = nullptr;
SANITIZER_HOOK_ATTRIBUTE const char *__asan_default_options() {
if (kAsanDefaultOptionsNaCl)
return kAsanDefaultOptionsNaCl;
return kAsanDefaultOptions;
}
extern char kASanDefaultSuppressions[];
SANITIZER_HOOK_ATTRIBUTE const char *__asan_default_suppressions() {
return kASanDefaultSuppressions;
}
#endif // defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_APPLE) ||
// defined(OS_WIN)
#endif // ADDRESS_SANITIZER
至此不難確認(rèn)潭苞,我們通過環(huán)境變量 ASAN_SYMBOLIZER_PATH
配置的 symbolizer,被 WebRTC 的代碼中的配置選項給覆蓋了真朗。
WebRTC 中相關(guān)的改動是 https://chromium.googlesource.com/chromium/src/build/+/919d061c2f455cc07b687a48322785b3b61f1455%5E%21/sanitizers/sanitizer_options.cc
這個 commit 提交的此疹。
對于這個問題,解決方案也不難確認(rèn)遮婶,把 WebRTC 的代碼 webrtc/build/sanitizers/sanitizer_options.cc
中蝗碎,配置 AddressSanitizer 的 symbolizer 的部分給去掉即可。
參考文檔