现代 C++ 跨平台开发-内存篇:内存分析工具
本文是整个【现代 C++ 跨平台开发-内存篇】系列的第 9 篇,主要涉及:内存分析工具。
现代 C++ 跨平台开发-内存篇:内存分析工具
静态分析
clang-tidy
LLVM 提供的静态代码扫描工具。
安装方式
VSCode 插件;
安卓和鸿蒙 NDK 均有内置;
iOS/macOS 可通过
brew install llvm安装;
参考配置
规则按 abi、performance、bugprone、cppcoreguidelines、modernize 等分组。
可通过根目录创建 .clang-tidy 文件自定义配置,通过 -FooGroup-BarConfig 禁用某条子规则。
Checks:
-*,
performance-*,
abi-*,
cert-*,
concurrency-*,
android-*,
objc-*,
bugprone-*,
-bugprone-easily-swappable-parameters,
cppcoreguidelines-*,
-cppcoreguidelines-avoid-do-while,
-cppcoreguidelines-prefer-member-initializer,
-cppcoreguidelines-avoid-non-const-global-variables,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-pro-type-reinterpret-cast,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-non-private-member-variables-in-classes,
modernize-*,
-modernize-use-trailing-return-type,
llvm-*,
-llvm-qualified-auto,
-llvm-namespace-comment,
-llvm-include-order,
readability-*,
-readability-qualified-auto,
-readability-identifier-length,
-readability-math-missing-parentheses,
-readability-convert-member-functions-to-static,
google-*,
-google-build-using-namespace,
-google-readability-namespace-comments,
misc-*,
-misc-non-private-member-variables-in-classes,
-misc-use-internal-linkage,
-misc-use-anonymous-namespace,
FormatStyle: llvm
手动忽略
如果某些地方需要手动忽略,可在注释中使用 NOLINT 相关声明:
class Foo {
// Suppress all the diagnostics for the line
Foo(int param); // NOLINT
// Consider explaining the motivation to suppress the warning
Foo(char param); // NOLINT: Allow implicit conversion from `char`, because <some valid reason>
// Silence only the specified checks for the line
Foo(double param); // NOLINT(google-explicit-constructor, google-runtime-int)
// Silence all checks from the `google` module
Foo(bool param); // NOLINT(google*)
// Silence all checks ending with `-avoid-c-arrays`
int array[10]; // NOLINT(*-avoid-c-arrays)
// Silence only the specified diagnostics for the next line
// NOLINTNEXTLINE(google-explicit-constructor, google-runtime-int)
Foo(bool param);
// Silence all checks from the `google` module for the next line
// NOLINTNEXTLINE(google*)
Foo(bool param);
// Silence all checks ending with `-avoid-c-arrays` for the next line
// NOLINTNEXTLINE(*-avoid-c-arrays)
int array[10];
// Silence only the specified checks for all lines between the BEGIN and END
// NOLINTBEGIN(google-explicit-constructor, google-runtime-int)
Foo(short param);
Foo(long param);
// NOLINTEND(google-explicit-constructor, google-runtime-int)
// Silence all checks from the `google` module for all lines between the BEGIN and END
// NOLINTBEGIN(google*)
Foo(bool param);
// NOLINTEND(google*)
// Silence all checks ending with `-avoid-c-arrays` for all lines between the BEGIN and END
// NOLINTBEGIN(*-avoid-c-arrays)
int array[10];
// NOLINTEND(*-avoid-c-arrays)
};
动态分析
Sanitizers
最初由 Google 开源的 Sanitizers 套件,现已成为 LLVM 项目的一部分,各家编译器也做了深度支持。
可用于运行时动态检测内存问题、资源竞争、UB 等,可直接在 CMakeLists.txt 配置。
不过要注意:
各平台编译器支持情况有差异;
有一定开销,建议仅在 debug 构建打开。
set(SANITIZERS "")
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
if(ANDROID AND "${CMAKE_ANDROID_ARCH_ABI}" STREQUAL "arm64-v8a")
list(APPEND SANITIZERS "hwaddress")
else()
list(APPEND SANITIZERS "address")
endif()
if(NOT WIN32)
list(APPEND SANITIZERS "undefined")
endif()
if(NOT WIN32 AND NOT EMSCRIPTEN AND NOT ANDROID AND NOT OHOS AND NOT APPLE)
list(APPEND SANITIZERS "leak")
endif()
# TSan is mutually exclusive with ASan and LSan.
# Enable it only if you strictly need to check for race conditions.
if(NOT WIN32 AND NOT EMSCRIPTEN AND NOT ANDROID AND NOT OHOS)
# list(APPEND SANITIZERS "thread")
endif()
if(SANITIZERS)
list(JOIN SANITIZERS "," SANITIZERS_STR)
if(MSVC)
set(SANITIZER_FLAGS "/fsanitize=${SANITIZERS_STR}")
else()
set(SANITIZER_FLAGS "-fsanitize=${SANITIZERS_STR}")
endif()
set_target_properties(${PROJECT_NAME} PROPERTIES
COMPILE_FLAGS "${SANITIZER_FLAGS} -fno-omit-frame-pointer -g"
LINK_FLAGS "${SANITIZER_FLAGS}"
)
endif()
endif()
基于软件的 AdSan
为每个字节分配一个“影子字节”,记录该地址是否可访问(如是否已分配、是否在有效范围内);
编译时插入额外检查代码,每次内存访问前,检查影子内存状态,若非法则立即中止并报告错误。
Android 的额外配置
依赖 NDK 目录的动态链接库(libclang_rt.asan-$abi-android.so),需拷贝到 jniLibs/$abi 目录;
需要接管整个进程内存,并且在所有 so 加载之前,需要在 resources/lib/$abi 目录 通过 wrap.sh 脚本手动加载上面的 so:
#!/system/bin/sh
LD_PRELOAD=libclang_rt.asan-aarch64-android.so exec "$@"
shell 脚本需确保拥有可执行权限,并且注意 Windows 编辑器下可能出现不兼容的换行符(会影响脚本执行);
AdSan 在 Android 上依赖帧指针,所以必须加上
-fno-omit-frame-pointer链接参数,确保错误报告可读。
Android 基于硬件的 AdSan
利用硬件特性(主要是 ARM64 的 Top Byte Ignore, TBI):
在 64 位指针的高 8 位(即“tag”)存储一个随机值;
分配内存时,同时为内存块分配一个 tag,并存储在影子内存中;
每次内存访问时,比较指针 tag 与目标地址影子内存中的 tag,不匹配则报错;
tag 针对的是 16 字节对齐的内存块,并且不需要全局唯一,只需要在当前所有活跃的内存块保持唯一,因此冲突概率并不高。
仍使用影子内存,但粒度更粗(通常为 16 字节对齐),因此内存开销更低。
仅支持具备 TBI 能力的平台:主要是 ARM64;
不依赖动态链接库,也无需 hook 启动流程。
XCode 启用 Sanitizers
XCode 深度集成 Sanitizer 相关工具链,只需 Build Settings 勾选对应的 Sanitizer,Instruments 可直接查看报告。
鸿蒙端 Sanitizers 支持
鸿蒙及 DevEco Studio 同样深度集成 Sanitizers,并且不需要任何额外配置(只需要 CMakeLists.txt 添加配置并且确保 debug 构建)。