目录

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. 行号问题

经过上述步骤后,可以检测出内存问题。当内存问题发生时,会上报堆栈,发现堆栈的行号有时候是错误的,目前还没有找到比较好的办法