ASAN 找不到 symbolizer 問題的分析與解決

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/linuxOpenRTCClient/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/linuxOpenRTCClient/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 的部分給去掉即可。

參考文檔

"compiler-rt" runtime libraries

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末旗扑,一起剝皮案震驚了整個濱河市蹦骑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌臀防,老刑警劉巖眠菇,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異袱衷,居然都是意外死亡捎废,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門致燥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來登疗,“玉大人,你說我怎么就攤上這事篡悟∶仗荆” “怎么了匾寝?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長荷腊。 經(jīng)常有香客問我艳悔,道長,這世上最難降的妖魔是什么女仰? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任猜年,我火速辦了婚禮,結(jié)果婚禮上疾忍,老公的妹妹穿的比我還像新娘乔外。我一直安慰自己,他們只是感情好一罩,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布杨幼。 她就那樣靜靜地躺著,像睡著了一般聂渊。 火紅的嫁衣襯著肌膚如雪差购。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天汉嗽,我揣著相機(jī)與錄音欲逃,去河邊找鬼。 笑死饼暑,一個胖子當(dāng)著我的面吹牛稳析,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播弓叛,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼彰居,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了撰筷?” 一聲冷哼從身側(cè)響起裕菠,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎闭专,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體旧烧,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡影钉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了掘剪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片平委。...
    茶點(diǎn)故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖夺谁,靈堂內(nèi)的尸體忽然破棺而出廉赔,到底是詐尸還是另有隱情肉微,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布蜡塌,位于F島的核電站碉纳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏馏艾。R本人自食惡果不足惜劳曹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望琅摩。 院中可真熱鬧铁孵,春花似錦、人聲如沸房资。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽轰异。三九已至岖沛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間溉浙,已是汗流浹背烫止。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留戳稽,地道東北人馆蠕。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像惊奇,于是被迫代替她去往敵國和親互躬。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容