Kotlin Native OHOS ASAN 适配

本文主要介绍如何让 kotlin/native 在适配了鸿蒙平台后,开启 asan 功能(kotlin 版本基于 2.0.21)
ASAN 简介
ASAN(Address Sanitizer)是针对 C/C++ 的快速内存错误检测工具,在运行时检测 C/C++ 代码中的多种内存错误
如:
heap-buffer-overflow stack-buffer-underflow stack-use-after-scope attempt-free-nonallocated-memory double-free heap-use-after-free Other categories
Kotlin/Native ASAN 支持情况
基于 kotlin 2.0.21 分支,我们可以发现如下代码:
// Sanitizer.kt
// 留下了口子,支持 asan 和 tsan
// 但是,实际几乎没有任何支持
enum class SanitizerKind {
ADDRESS,
THREAD,
}
// KonanConfig.kt
val sanitizer = configuration.get(BinaryOptions.sanitizer)?.takeIf {
when {
// 如果不是 TASAN 直接 not support
it != SanitizerKind.THREAD -> "${it.name} sanitizer is not supported yet"
produce == CompilerOutputKind.STATIC -> "${it.name} sanitizer is unsupported for static library"
produce == CompilerOutputKind.FRAMEWORK && produceStaticFramework -> "${it.name} sanitizer is unsupported for static framework"
it !in target.supportedSanitizers() -> "${it.name} sanitizer is unsupported on ${target.name}"
else -> null
}?.let { message ->
configuration.report(CompilerMessageSeverity.STRONG_WARNING, message)
return@takeIf false
}
return@takeIf true
}
// KonanTargetExtensions
// iOS 几乎不支持
fun KonanTarget.supportedSanitizers(): List<SanitizerKind> =
when(this) {
is KonanTarget.LINUX_X64 -> listOf(SanitizerKind.ADDRESS)
is KonanTarget.MACOS_X64 -> listOf(SanitizerKind.THREAD)
// TODO: Enable ASAN on macOS. Currently there's an incompatibility between clang frontend version and clang_rt.asan version.
// TODO: Enable TSAN on linux. Currently there's a link error between clang_rt.tsan and libstdc++.
// TODO: Consider supporting mingw.
// TODO: Support macOS arm64
else -> listOf()
}
综上所述,虽然留了 asan 和 tsan 的口子,但是目前 asan 和 tsan 几乎不支持
添加 OHOS ASAN 支持
默认情况下,鸿蒙的 clang 通过以下命令可以开启 asan
// 一条命令编译出 binary
/Users/ptrainbow/.konan/dependencies/ohos-ndk-3.2.4/native/llvm/bin/clang \
--target=aarch64-linux-ohos \
-shared-libasan \
-fsanitize=address \
/Users/ptrainbow/work/tripkotlin/main.cpp \
-o \
/Users/ptrainbow/work/tripkotlin/libhello.out
通过 clang -v 查看详细步骤
// 编译
"/Users/ptrainbow/.konan/dependencies/ohos-ndk-3.2.4/native/llvm/bin/clang-12"
-cc1
-triple
aarch64-unknown-linux-ohos
-emit-obj
-mrelax-all
-mnoexecstack
-disable-free
-disable-llvm-verifier
-discard-value-names
-main-file-name
main.cpp
-mrelocation-model
pic
-pic-level
2
-pic-is-pie
-mframe-pointer=non-leaf
-fno-rounding-math
-mconstructor-aliases
-munwind-tables
-target-cpu
generic
-target-feature
+neon
-target-abi
aapcs
-mllvm
-aarch64-fix-cortex-a53-835769=1
-fallow-half-arguments-and-returns
-fno-split-dwarf-inlining
-debugger-tuning=gdb
-target-linker-version
86
-v
-resource-dir
/Users/ptrainbow/.konan/dependencies/ohos-ndk-3.2.4/native/llvm/lib/clang/12.0.1
-internal-isystem
/Users/ptrainbow/.konan/dependencies/ohos-ndk-3.2.4/native/llvm/bin/../include/libcxx-ohos/include/c++/v1
-internal-isystem
/Users/ptrainbow/.konan/dependencies/ohos-ndk-3.2.4/native/llvm/lib/clang/12.0.1/include
-internal-externc-isystem
/Users/ptrainbow/.konan/dependencies/ohos-ndk-3.2.4/native/llvm/bin/../../sysroot/usr/include/aarch64-linux-ohos
-internal-externc-isystem
/Users/ptrainbow/.konan/dependencies/ohos-ndk-3.2.4/native/llvm/bin/../../sysroot/include
-internal-externc-isystem
/Users/ptrainbow/.konan/dependencies/ohos-ndk-3.2.4/native/llvm/bin/../../sysroot/usr/include
-fdeprecated-macro
-fdebug-compilation-dir
/Users/ptrainbow/work/tripkotlin
-ferror-limit
19
-fsanitize=address
-fsanitize-system-blacklist=/Users/ptrainbow/.konan/dependencies/ohos-ndk-3.2.4/native/llvm/lib/clang/12.0.1/share/asan_blacklist.txt
-fsanitize-address-use-after-scope
-fno-assume-sane-operator-new
-fno-signed-char
-fgnuc-version=4.2.1
-fcxx-exceptions
-fexceptions
-fcolor-diagnostics
-faddrsig
-o
/var/folders/qk/67k07kls3c54r3q5zmfm9r200000gr/T/main-d90aaa.o
-x
c++
/Users/ptrainbow/work/tripkotlin/main.cpp
// 链接
"/Users/ptrainbow/.konan/dependencies/ohos-ndk-3.2.4/native/llvm/bin/ld.lld"
-pie
-z
noexecstack
-EL
--fix-cortex-a53-843419
--warn-shared-textrel
-z
now
-z
relro
-z
max-page-size=4096
--hash-style=gnu
--hash-style=both
--enable-new-dtags
--eh-frame-hdr
-m
aarch64linux
-dynamic-linker
/lib/ld-musl-aarch64.so.1
-o
/Users/ptrainbow/work/tripkotlin/libhello.out
/Users/ptrainbow/.konan/dependencies/ohos-ndk-3.2.4/native/llvm/bin/../../sysroot/usr/lib/aarch64-linux-ohos/Scrt1.o
/Users/ptrainbow/.konan/dependencies/ohos-ndk-3.2.4/native/llvm/bin/../../sysroot/usr/lib/aarch64-linux-ohos/crti.o
/Users/ptrainbow/.konan/dependencies/ohos-ndk-3.2.4/native/llvm/lib/clang/12.0.1/lib/aarch64-linux-ohos/clang_rt.crtbegin.o
-L/Users/ptrainbow/.konan/dependencies/ohos-ndk-3.2.4/native/llvm/lib/clang/12.0.1/lib/aarch64-linux-ohos
-L/Users/ptrainbow/.konan/dependencies/ohos-ndk-3.2.4/native/llvm/bin/../../sysroot/usr/lib/
-L/Users/ptrainbow/.konan/dependencies/ohos-ndk-3.2.4/native/llvm/bin/../lib/aarch64-linux-ohos/
-L/Users/ptrainbow/.konan/dependencies/ohos-ndk-3.2.4/native/llvm/bin/../../sysroot/usr/lib/aarch64-linux-ohos/
/Users/ptrainbow/.konan/dependencies/ohos-ndk-3.2.4/native/llvm/lib/clang/12.0.1/lib/aarch64-linux-ohos/libclang_rt.asan.so
--whole-archive
/Users/ptrainbow/.konan/dependencies/ohos-ndk-3.2.4/native/llvm/lib/clang/12.0.1/lib/aarch64-linux-ohos/libclang_rt.asan-preinit.a
--no-whole-archive
/var/folders/qk/67k07kls3c54r3q5zmfm9r200000gr/T/main-d90aaa.o
/Users/ptrainbow/.konan/dependencies/ohos-ndk-3.2.4/native/llvm/lib/clang/12.0.1/lib/aarch64-linux-ohos/libclang_rt.builtins.a
-l:libunwind.a
-lc
/Users/ptrainbow/.konan/dependencies/ohos-ndk-3.2.4/native/llvm/lib/clang/12.0.1/lib/aarch64-linux-ohos/libclang_rt.builtins.a
-l:libunwind.a
/Users/ptrainbow/.konan/dependencies/ohos-ndk-3.2.4/native/llvm/lib/clang/12.0.1/lib/aarch64-linux-ohos/clang_rt.crtend.o
/Users/ptrainbow/.konan/dependencies/ohos-ndk-3.2.4/native/llvm/bin/../../sysroot/usr/lib/aarch64-linux-ohos/crtn.o
但是 kotlin/native 并不是标准编译 c/cpp 流程,而是使用 ir 作为输入 .o 作为输出,然后使用连接器自己链接,命令类似如下:
// kotlin/native 分步编译
// 编译
clang -cc1 -emit-obj -disable-llvm-optzns -x ir -mrelocation-model pic xxxx 省略 test.bc -o test.o
// 链接
lld xxxx 省略 -o libtest.so
如何等价转换成 devevo 编译 cpp 的 clang 命令呢?
经过一番研究和咨询,发现以下几个问题:
1. disable-llvm-optzns 问题
因为 kotlin/native 默认开启 disable-llvm-optzns 选项,走自己的 optimizePipeline。但是 asan 似乎依赖 optimize,总之一旦开启这个选项,asan 插桩就会失败,编译出的 so 中没有任何 asan 相关的符号
2. llvm bitcode 插桩顺序问题
基于 1,在去掉 -disable-llvm-optzns 后,发现编译出的 so 的有 asan 相关的符号,但是不全,只有 init 相关函数,看不到 report 等符号
最终排查到一个 issue:
Sanitizers won’t trigger when compiling from llvm’s bitcode #1476
所以,从 bitcode 编译无法触发 sanitizer 插桩,因为 sanitizer 的插桩依赖于 asan llvm attribute,而 attribute 如果不走 clang 完整的命令,就需要自己去处理。而 kotlin/native 刚好是拆分了整个编译和链接的流程,所以我们需要使用 llvm api 处理 llvm ir 来实现 asan 功能
3. llvm api 使用问题
对于 llvm api,外行不懂的情况下,显然是处理不了了。好在 asan 和 tsan 等功能是基础功能,所以在低版本的 llvm(12.X) 中提供了封装好的 utils 方法
详见:llvm-project/clang/lib/CodeGen/BackendUtil.cpp 中的 addAddressSanitizerPasses
这里直接 copy 即可
4. 适配到 kotlin/native 问题
需要修改之前提到的 kotlin/native 中 sanitizer 相关的代码,主要作用是,放开 ohos 的 asan 支持
然后就是,在 CAPIExtension.cpp 增加 llvm api AddressSanitize 相关的头文件,使用 BackendUtil.cpp 类似代码即可
5. 行号问题
经过上述步骤后,可以检测出内存问题。当内存问题发生时,会上报堆栈,发现堆栈的行号有时候是错误的,目前还没有找到比较好的办法