From fc754866112d8a7da4a93c48474f3b0f51dac18d Mon Sep 17 00:00:00 2001 From: Pavel Kunyavskiy Date: Thu, 17 Jun 2021 18:15:20 +0300 Subject: [PATCH] [K/N] Debug tool for checking state at call points of unknown functions After linking runtime, llvm-ir is modified to add checker function call at all points where unknown function, which can possibly run long is called. This function checks Native state is set, to avoid long locks at gc. --- .../backend/konan/CheckExternalCalls.kt | 201 ++++++++++ .../kotlin/backend/konan/ToplevelPhases.kt | 4 + .../backend/konan/llvm/BitcodePhases.kt | 14 + .../kotlin/backend/konan/llvm/LlvmUtils.kt | 10 + .../kotlin/backend/konan/llvm/StaticData.kt | 6 + kotlin-native/runtime/src/main/cpp/Common.h | 2 + .../runtime/src/main/cpp/ObjCInterop.mm | 2 +- .../runtime/src/main/cpp/Porting.cpp | 8 +- .../runtime/src/mm/cpp/CallsChecker.cpp | 378 ++++++++++++++++++ kotlin-native/runtime/src/mm/cpp/Memory.cpp | 3 + .../runtime/src/mm/cpp/ThreadSuspension.cpp | 2 +- 11 files changed, 624 insertions(+), 6 deletions(-) create mode 100644 kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CheckExternalCalls.kt create mode 100644 kotlin-native/runtime/src/mm/cpp/CallsChecker.cpp diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CheckExternalCalls.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CheckExternalCalls.kt new file mode 100644 index 00000000000..e3790cd3cf1 --- /dev/null +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CheckExternalCalls.kt @@ -0,0 +1,201 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.backend.konan + +import kotlinx.cinterop.toCValues +import llvm.* +import org.jetbrains.kotlin.backend.konan.llvm.* + +private fun getBasicBlocks(function: LLVMValueRef) = + generateSequence(LLVMGetFirstBasicBlock(function)) { LLVMGetNextBasicBlock(it) } + +private fun getInstructions(function: LLVMBasicBlockRef) = + generateSequence(LLVMGetFirstInstruction(function)) { LLVMGetNextInstruction(it) } + +private fun LLVMValueRef.isFunctionCall() = LLVMIsACallInst(this) != null || LLVMIsAInvokeInst(this) != null + +private fun LLVMValueRef.isExternalFunction() = LLVMGetFirstBasicBlock(this) == null + + +private fun LLVMValueRef.isLLVMBuiltin(): Boolean { + val name = this.name ?: return false + return name.startsWith("llvm.") +} + + +private class CallsChecker(val context: Context, goodFunctions: List) { + private val goodFunctionsExact = goodFunctions.filterNot { it.endsWith("*") }.toSet() + private val goodFunctionsByPrefix = goodFunctions.filter { it.endsWith("*") }.map { it.substring(0, it.length - 1) }.sorted() + + private fun isGoodFunction(name: String) : Boolean { + if (name in goodFunctionsExact) return true + val insertionPoint = goodFunctionsByPrefix.binarySearch(name).let { if (it < 0) it.inv() else it } + if (insertionPoint < goodFunctionsByPrefix.size && name.startsWith(goodFunctionsByPrefix[insertionPoint])) return true + if (insertionPoint > 0 && name.startsWith(goodFunctionsByPrefix[insertionPoint - 1])) return true + return false + } + + private fun externalFunction(name: String, type: LLVMTypeRef) = + context.llvm.externalFunction(name, type, context.stdlibModule.llvmSymbolOrigin) + + private fun moduleFunction(name: String) = + LLVMGetNamedFunction(context.llvmModule, name) ?: throw IllegalStateException("$name function is not available") + + val getMethodImpl = externalFunction("class_getMethodImplementation", functionType(pointerType(functionType(voidType, false)), false, int8TypePtr, int8TypePtr)) + val getClass = externalFunction("object_getClass", functionType(int8TypePtr, false, int8TypePtr)) + val getSuperClass = externalFunction("class_getSuperclass", functionType(int8TypePtr, false, int8TypePtr)) + val checkerFunction = moduleFunction("Kotlin_mm_checkStateAtExternalFunctionCall") + + private data class ExternalCallInfo(val name: String?, val calledPtr: LLVMValueRef) + + private fun LLVMValueRef.getPossiblyExternalCalledFunction(): ExternalCallInfo? { + fun isIndirectCallArgument(value: LLVMValueRef) = LLVMIsALoadInst(value) != null || LLVMIsAArgument(value) != null || + LLVMIsAPHINode(value) != null || LLVMIsASelectInst(value) != null || LLVMIsACallInst(value) != null + + fun cleanCalledFunction(value: LLVMValueRef): ExternalCallInfo? { + return when { + LLVMIsAFunction(value) != null -> { + val valueOrSpecial = value.takeIf { !it.isLLVMBuiltin() } + ?: LLVMConstIntToPtr(Int64(CALLED_LLVM_BUILTIN).llvm, int8TypePtr)!! + ExternalCallInfo(value.name!!, valueOrSpecial).takeIf { value.isExternalFunction() } + } + LLVMIsACastInst(value) != null -> cleanCalledFunction(LLVMGetOperand(value, 0)!!) + isIndirectCallArgument(value) -> ExternalCallInfo(null, value) // this is a callback call + LLVMIsAInlineAsm(value) != null -> null // this is inline assembly call + LLVMIsAConstantExpr(value) != null -> { + when (LLVMGetConstOpcode(value)) { + LLVMOpcode.LLVMBitCast -> cleanCalledFunction(LLVMGetOperand(value, 0)!!) + else -> TODO("not implemented constant type in call") + } + } + LLVMIsAGlobalAlias(value) != null -> cleanCalledFunction(LLVMAliasGetAliasee(value)!!) + else -> { + TODO("not implemented call argument ${llvm2string(value)} called in ${llvm2string(this)}") + } + } + } + + return cleanCalledFunction(LLVMGetCalledValue(this)!!) + } + + private fun processBasicBlock(functionName: String, block: LLVMBasicBlockRef) { + val calls = getInstructions(block) + .filter { it.isFunctionCall() } + .toList() + val builder = LLVMCreateBuilderInContext(llvmContext) + + for (call in calls) { + val calleeInfo = call.getPossiblyExternalCalledFunction() ?: continue + if (calleeInfo.name != null && isGoodFunction(calleeInfo.name)) continue + LLVMPositionBuilderBefore(builder, call) + LLVMBuilderResetDebugLocation(builder) + val callSiteDescription: String + val calledName: String? + val calledPtrLlvm: LLVMValueRef? + when (calleeInfo.name) { + "objc_msgSend" -> { + // objc_msgSend has wrong declaration in header, so generated wrapper is strange, Let's just skip it + if (LLVMGetNumArgOperands(call) < 2) continue + callSiteDescription = "$functionName (over objc_msgSend)" + calledName = null + val firstArgI8Ptr = LLVMBuildBitCast(builder, LLVMGetArgOperand(call, 0), int8TypePtr, "") + val firstArgClassPtr = LLVMBuildCall(builder, getClass, listOf(firstArgI8Ptr).toCValues(), 1, "") + val isNil = LLVMBuildICmp(builder, LLVMIntPredicate.LLVMIntEQ, firstArgI8Ptr, LLVMConstNull(int8TypePtr), "") + val selector = LLVMGetArgOperand(call, 1) + val calledPtrLlvmIfNotNilFunPtr = LLVMBuildCall(builder, getMethodImpl, listOf(firstArgClassPtr, selector).toCValues(), 2, "") + val calledPtrLlvmIfNotNil = LLVMBuildBitCast(builder, calledPtrLlvmIfNotNilFunPtr, int8TypePtr, "") + val calledPtrLlvmIfNil = LLVMConstIntToPtr(Int64(MSG_SEND_TO_NULL).llvm, int8TypePtr) + calledPtrLlvm = LLVMBuildSelect(builder, isNil, calledPtrLlvmIfNil, calledPtrLlvmIfNotNil, "") + } + "objc_msgSendSuper2" -> { + if (LLVMGetNumArgOperands(call) < 2) continue + callSiteDescription = "$functionName (over objc_msgSendSuper2)" + calledName = null + val superStruct = LLVMGetArgOperand(call, 0) + val superClassPtrPtr = LLVMBuildGEP(builder, superStruct, listOf(Int32(0).llvm, Int32(1).llvm).toCValues(), 2, "") + val superClassPtr = LLVMBuildLoad(builder, superClassPtrPtr, "") + val classPtr = LLVMBuildCall(builder, getSuperClass, listOf(superClassPtr).toCValues(), 1, "") + val calledPtrLlvmFunPtr = LLVMBuildCall(builder, getMethodImpl, listOf(classPtr, LLVMGetArgOperand(call, 1)).toCValues(), 2, "") + calledPtrLlvm = LLVMBuildBitCast(builder, calledPtrLlvmFunPtr, int8TypePtr, "") + } + else -> { + callSiteDescription = functionName + calledName = calleeInfo.name + calledPtrLlvm = LLVMBuildBitCast(builder, calleeInfo.calledPtr, int8TypePtr, "") + } + } + val callSiteDescriptionLlvm = context.llvm.staticData.cStringLiteral(callSiteDescription).llvm + val calledNameLlvm = if (calledName == null) LLVMConstNull(int8TypePtr) else context.llvm.staticData.cStringLiteral(calledName).llvm + LLVMBuildCall(builder, checkerFunction, listOf(callSiteDescriptionLlvm, calledNameLlvm, calledPtrLlvm).toCValues(), 3, "") + } + LLVMDisposeBuilder(builder) + } + + fun processFunction(function: LLVMValueRef) { + if (function == checkerFunction) return + getBasicBlocks(function).forEach { + processBasicBlock(function.name!!, it) + } + } + + companion object { + const val MSG_SEND_TO_NULL: Long = -1 + const val CALLED_LLVM_BUILTIN: Long = -2 + } +} + +private const val functionListGlobal = "Kotlin_callsCheckerKnownFunctions" +private const val functionListSizeGlobal = "Kotlin_callsCheckerKnownFunctionsCount" + +internal fun checkLlvmModuleExternalCalls(context: Context) { + val staticData = context.llvm.staticData + + val annotations = staticData.getGlobal("llvm.global.annotations")?.getInitializer() + + val ignoredFunctions = annotations?.run { + getOperands(this).mapNotNull { + val annotationName = LLVMGetInitializer(LLVMGetOperand(LLVMGetOperand(it, 1), 0))?.getAsCString() + if (annotationName == "no_external_calls_check") { + LLVMGetOperand(LLVMGetOperand(it, 0), 0)!!.name + } else { + null + } + }.toSet() + } ?: emptySet() + + val goodFunctions = staticData.getGlobal("Kotlin_callsCheckerGoodFunctionNames")?.getInitializer()?.run { + getOperands(this).map { + LLVMGetInitializer(LLVMGetOperand(it, 0))!!.getAsCString() + }.toList() + } ?: emptyList() + + val checker = CallsChecker(context, goodFunctions) + getFunctions(context.llvmModule!!) + .filter { !it.isExternalFunction() && it.name !in ignoredFunctions } + .forEach(checker::processFunction) + // otherwise optimiser can inline it + staticData.getGlobal(functionListGlobal)?.setExternallyInitialized(true); + staticData.getGlobal(functionListSizeGlobal)?.setExternallyInitialized(true); + context.verifyBitCode() +} + +// this should be a separate pass, to handle DCE correctly +internal fun addFunctionsListSymbolForChecker(context: Context) { + val staticData = context.llvm.staticData + + val functions = getFunctions(context.llvmModule!!) + .filter { !it.isExternalFunction() } + .map { constPointer(it).bitcast(int8TypePtr) } + .toList() + val functionsArray = staticData.placeGlobalConstArray("", int8TypePtr, functions) + staticData.getGlobal(functionListGlobal) + ?.setInitializer(functionsArray) + ?: throw IllegalStateException("$functionListGlobal global not found") + staticData.getGlobal(functionListSizeGlobal) + ?.setInitializer(Int32(functions.size)) + ?: throw IllegalStateException("$functionListSizeGlobal global not found") + context.verifyBitCode() +} \ No newline at end of file diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt index dfc92e72011..37ea3d6f1e6 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt @@ -404,7 +404,9 @@ private val backendCodegen = namedUnitPhase( verifyBitcodePhase then printBitcodePhase then linkBitcodeDependenciesPhase then + checkExternalCallsPhase then bitcodeOptimizationPhase then + rewriteExternalCallsCheckerGlobals then unitSink() ) @@ -453,6 +455,8 @@ internal fun PhaseConfig.konanPhasesConfig(config: KonanConfig) { disableIf(backendCodegen, config.produce == CompilerOutputKind.LIBRARY) disableUnless(bitcodeOptimizationPhase, config.produce.involvesLinkStage) disableUnless(linkBitcodeDependenciesPhase, config.produce.involvesLinkStage) + disableUnless(checkExternalCallsPhase, config.produce.involvesLinkStage && getBoolean(KonanConfigKeys.CHECK_EXTERNAL_CALLS)) + disableUnless(rewriteExternalCallsCheckerGlobals, config.produce.involvesLinkStage && getBoolean(KonanConfigKeys.CHECK_EXTERNAL_CALLS)) disableUnless(objectFilesPhase, config.produce.involvesLinkStage) disableUnless(linkerPhase, config.produce.involvesLinkStage) disableIf(testProcessorPhase, getNotNull(KonanConfigKeys.GENERATE_TEST_RUNNER) == TestRunnerKind.NONE) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BitcodePhases.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BitcodePhases.kt index eca03758f86..4a620c0c162 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BitcodePhases.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BitcodePhases.kt @@ -300,6 +300,20 @@ internal val linkBitcodeDependenciesPhase = makeKonanModuleOpPhase( op = { context, _ -> linkBitcodeDependencies(context) } ) +internal val checkExternalCallsPhase = makeKonanModuleOpPhase( + name = "CheckExternalCalls", + description = "Check external calls", + op = { context, _ -> checkLlvmModuleExternalCalls(context) } +) + +internal val rewriteExternalCallsCheckerGlobals = makeKonanModuleOpPhase( + name = "RewriteExternalCallsCheckerGlobals", + description = "Rewrite globals for external calls checker after optimizer run", + op = { context, _ -> addFunctionsListSymbolForChecker(context) } +) + + + internal val bitcodeOptimizationPhase = makeKonanModuleOpPhase( name = "BitcodeOptimization", description = "Optimize bitcode", diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt index 29d536b4e8a..75fae809bff 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt @@ -230,6 +230,16 @@ internal fun RuntimeAware.isObjectType(type: LLVMTypeRef): Boolean { internal fun CArrayPointer.getBytes(size: Long) = (0 .. size-1).map { this[it] }.toByteArray() +internal fun LLVMValueRef.getAsCString() : String { + memScoped { + val lengthPtr = alloc() + val data = LLVMGetAsString(this@getAsCString, lengthPtr.ptr)!! + require(lengthPtr.value >= 1 && data[lengthPtr.value - 1] == 0.toByte()) { "Expected null-terminated string from llvm"} + return data.toKString() + } +} + + internal fun getFunctionType(ptrToFunction: LLVMValueRef): LLVMTypeRef { return getGlobalType(ptrToFunction) } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt index 503b5dba3c3..cb32c839d80 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt @@ -60,6 +60,8 @@ internal class StaticData(override val context: Context): ContextUtils { val type get() = getGlobalType(this.llvmGlobal) + fun getInitializer() = LLVMGetInitializer(llvmGlobal) + fun setInitializer(value: ConstValue) { LLVMSetInitializer(llvmGlobal, value.llvm) } @@ -84,6 +86,10 @@ internal class StaticData(override val context: Context): ContextUtils { LLVMSetSection(llvmGlobal, name) } + fun setExternallyInitialized(value: Boolean) { + LLVMSetExternallyInitialized(llvmGlobal, if (value) 1 else 0) + } + val pointer = Pointer.to(this) } diff --git a/kotlin-native/runtime/src/main/cpp/Common.h b/kotlin-native/runtime/src/main/cpp/Common.h index 71b44af940e..3f3eb20fd61 100644 --- a/kotlin-native/runtime/src/main/cpp/Common.h +++ b/kotlin-native/runtime/src/main/cpp/Common.h @@ -27,6 +27,8 @@ #define ALWAYS_INLINE __attribute__((always_inline)) #define NO_INLINE __attribute__((noinline)) +#define NO_EXTERNAL_CALLS_CHECK __attribute__((annotate("no_external_calls_check"))) + #if KONAN_NO_THREADS #define THREAD_LOCAL_VARIABLE #else diff --git a/kotlin-native/runtime/src/main/cpp/ObjCInterop.mm b/kotlin-native/runtime/src/main/cpp/ObjCInterop.mm index 4991acf3bcb..ffd73c3ff58 100644 --- a/kotlin-native/runtime/src/main/cpp/ObjCInterop.mm +++ b/kotlin-native/runtime/src/main/cpp/ObjCInterop.mm @@ -256,7 +256,7 @@ static void AddMethods(Class clazz, const struct ObjCMethodDescription* methods, static kotlin::SpinLock classCreationMutex; static int anonymousClassNextId = 0; -static Class allocateClass(const KotlinObjCClassInfo* info) { +NO_EXTERNAL_CALLS_CHECK static Class allocateClass(const KotlinObjCClassInfo* info) { Class superclass = Kotlin_Interop_getObjCClass(info->superclassName); if (info->exported) { diff --git a/kotlin-native/runtime/src/main/cpp/Porting.cpp b/kotlin-native/runtime/src/main/cpp/Porting.cpp index 4253d1c46a7..4b58dc57dc6 100644 --- a/kotlin-native/runtime/src/main/cpp/Porting.cpp +++ b/kotlin-native/runtime/src/main/cpp/Porting.cpp @@ -68,7 +68,7 @@ void consoleWriteUtf8(const char* utf8, uint32_t sizeBytes) { #endif } -void consoleErrorUtf8(const char* utf8, uint32_t sizeBytes) { +NO_EXTERNAL_CALLS_CHECK void consoleErrorUtf8(const char* utf8, uint32_t sizeBytes) { #ifdef KONAN_ANDROID // TODO: use sizeBytes! __android_log_print(ANDROID_LOG_ERROR, "Konan_main", "%s", utf8); @@ -148,7 +148,7 @@ extern "C" int rpl_vsnprintf(char *, size_t, const char *, va_list); #define vsnprintf_impl ::vsnprintf #endif -void consolePrintf(const char* format, ...) { +NO_EXTERNAL_CALLS_CHECK void consolePrintf(const char* format, ...) { char buffer[1024]; va_list args; va_start(args, format); @@ -160,7 +160,7 @@ void consolePrintf(const char* format, ...) { } // TODO: Avoid code duplication. -void consoleErrorf(const char* format, ...) { +NO_EXTERNAL_CALLS_CHECK void consoleErrorf(const char* format, ...) { char buffer[1024]; va_list args; va_start(args, format); @@ -201,7 +201,7 @@ static void onThreadExitCallback(void* value) { } } -bool isOnThreadExitNotSetOrAlreadyStarted() { +NO_EXTERNAL_CALLS_CHECK bool isOnThreadExitNotSetOrAlreadyStarted() { return terminationKey != 0 && pthread_getspecific(terminationKey) == nullptr; } diff --git a/kotlin-native/runtime/src/mm/cpp/CallsChecker.cpp b/kotlin-native/runtime/src/mm/cpp/CallsChecker.cpp new file mode 100644 index 00000000000..1d6ad0f7b2a --- /dev/null +++ b/kotlin-native/runtime/src/mm/cpp/CallsChecker.cpp @@ -0,0 +1,378 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +#include +#include + +#include "KAssert.h" +#include "Memory.h" +#include "Porting.h" +#include "ThreadData.hpp" +#include "ThreadRegistry.hpp" +#include "ExecFormat.h" + +using namespace kotlin; + +// this values will be substituted by compiler +extern "C" const void **Kotlin_callsCheckerKnownFunctions = nullptr; +extern "C" int Kotlin_callsCheckerKnownFunctionsCount = 0; + +extern "C" const char* Kotlin_callsCheckerGoodFunctionNames[] = { + "\x01_close", + "\x01_mprotect", + "close", + "mprotect", + + "_ZL15_objc_terminatev", // _objc_terminate() + "_ZNKSt8__detail20_Prime_rehash_policy14_M_need_rehashEmmm", // std::__detail::_Prime_rehash_policy::_M_need_rehash(unsigned long, unsigned long, unsigned long) const + "_ZNKSt8__detail20_Prime_rehash_policy14_M_need_rehashEyyy", // std::__detail::_Prime_rehash_policy::_M_need_rehash(unsigned long long, unsigned long long, unsigned long long) const + "_ZNSaIcED2Ev", // std::allocator::~allocator() + "_ZNSt13exception_ptrC1ERKS_", // std::exception_ptr::exception_ptr(std::exception_ptr const&) + "_ZNSt13exception_ptrD1Ev", // std::exception_ptr::~exception_ptr() + "_ZNSt15__exception_ptr13exception_ptrC1ERKS0_", // std::__exception_ptr::exception_ptr::exception_ptr(std::__exception_ptr::exception_ptr const&) + "_ZNSt15__exception_ptr13exception_ptrD1Ev", // std::__exception_ptr::exception_ptr::~exception_ptr() + "_ZNSt18condition_variableD1Ev", // std::condition_variable::~condition_variable() + "_ZNSt3__112__next_primeEm", // std::__1::__next_prime(unsigned long) + "_ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEED1Ev", // std::__1::basic_string, std::__1::allocator >::~basic_string() + "_ZNSt3__16chrono12steady_clock3nowEv", // std::__1::chrono::steady_clock::now() + "_ZNSt3__19to_stringEi", // std::__1::to_string(int) + "_ZNSt6chrono3_V212steady_clock3nowEv", // std::chrono::_V2::steady_clock::now() + "_ZNSt8__detail15_List_node_base7_M_hookEPS0_", // std::__detail::_List_node_base::_M_hook(std::__detail::_List_node_base*) + "_ZNSt8__detail15_List_node_base9_M_unhookEv", // std::__detail::_List_node_base::_M_unhook() + "_ZNSt9exceptionD2Ev", // std::exception::~exception() + "_ZSt17current_exceptionv", // std::current_exception() + "_ZSt17rethrow_exceptionSt13exception_ptr", // std::rethrow_exception(std::exception_ptr) + "_ZSt29_Rb_tree_insert_and_rebalancebPSt18_Rb_tree_node_baseS0_RS_", // std::_Rb_tree_insert_and_rebalance(bool, std::_Rb_tree_node_base*, std::_Rb_tree_node_base*, std::_Rb_tree_node_base&) + "_ZSt9terminatev", // std::terminate() + "_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEED1Ev", // std::__cxx11::basic_string, std::allocator >::~basic_string() + "_ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE", // std::rethrow_exception(std::__exception_ptr::exception_ptr) + "_ZSt28_Rb_tree_rebalance_for_erasePSt18_Rb_tree_node_baseRS_", // std::_Rb_tree_rebalance_for_erase(std::_Rb_tree_node_base*, std::_Rb_tree_node_base&) + "_ZN9__gnu_cxx27__verbose_terminate_handlerEv", // __gnu_cxx::__verbose_terminate_handler() + "__mingw_vsnprintf", + "__cxa_allocate_exception", + "__cxa_begin_catch", + "__cxa_end_catch", + "__cxa_throw", + "__memset_chk", + + "abort", + "acos", + "acosf", + "acosh", + "acoshf", + "asin", + "asinf", + "asinh", + "asinhf", + "atan", + "atanf", + "atan2", + "atan2f", + "atanf", + "atanh", + "atanhf", + "calloc", + "clock_gettime", + "cos", + "cosf", + "cosh", + "cosh", + "coshf", + "coshf", + "exit", + "exp", + "expf", + "expm1", + "expm1f", + "free", + "getrusage", + "hypot", + "hypotf", + "isinf", + "isnan", + "log", + "logf", + "log1p", + "log1pf", + "log10", + "log10f", + "log2", + "log2f", + "malloc", + "memcmp", + "memmem", + "nextafter", + "nextafterf", + "pow", + "powf", + "remainder", + "remainderf", + "sin", + "sinf", + "sinh", + "sinhf", + "snprintf", + "sqrt", + "sqrtf", + "strcmp", + "strlen", + "strnlen", + "tan", + "tanf", + "tanh", + "tanhf", + "vsnprintf", + + "dispatch_once", + "\x01_pthread_cond_init", + "_pthread_cond_init", + "pthread_cond_broadcast", + "pthread_cond_destroy", + "pthread_cond_signal", + "pthread_cond_init", + "pthread_create", + "pthread_equal", + "pthread_main_np", + "pthread_mutex_destroy", + "pthread_mutex_init", + "pthread_mutex_unlock", + "pthread_self", + + "+[NSError errorWithDomain:code:userInfo:]", + "+[NSMethodSignature signatureWithObjCTypes:]", + "+[NSNull null]", + "+[NSObject allocWithZone:]", + "+[NSObject class]", + "+[NSObject conformsToProtocol:]", + "+[NSObject isKindOfClass:]", + "+[NSObject new]", + "+[NSString stringWithFormat:]", + "+[NSString stringWithUTF8String:]", + "+[NSValue valueWithPointer:]", + "-[NSDictionary objectForKeyedSubscript:]", + "-[NSError localizedDescription]", + "-[NSError userInfo]", + "-[NSException name]", + "-[NSException reason]", + "-[NSMethodSignature getArgumentTypeAtIndex:]", + "-[NSMethodSignature methodReturnType]", + "-[NSMethodSignature numberOfArguments]", + "-[NSObject conformsToProtocol:]", + "-[NSObject init]", + "-[NSObject isKindOfClass:]", + "-[NSObject retain]", + "-[NSPlaceholderString initWithBytes:length:encoding:]", + "-[NSPlaceholderString initWithBytesNoCopy:length:encoding:freeWhenDone:]", + "-[NSValue pointerValue]", + "-[__NSArray0 count]", + "-[__NSArrayI count]", + "-[__NSArrayI objectAtIndex:]", + "-[__NSArrayM count]", + "-[__NSArrayM objectAtIndex:]", + "-[__NSCFBoolean boolValue]", + "-[__NSCFNumber doubleValue]", + "-[__NSCFNumber floatValue]", + "-[__NSCFNumber intValue]", + "-[__NSCFNumber longLongValue]", + "-[__NSCFNumber objCType]", + "-[__NSCFString isEqual:]", + "-[__NSDictionaryM setObject:forKeyedSubscript:]", + "-[__NSFrozenDictionaryM objectForKeyedSubscript:]", + "-[__SwiftNativeNSError userInfo]", + "CFStringCreateCopy", + "CFStringGetCharacters", + "CFStringGetLength", + "class_addIvar", + "class_addMethod", + "class_addProtocol", + "class_copyMethodList", + "class_copyProtocolList", + "class_getClassMethod", + "class_getInstanceMethod", + "class_getInstanceVariable", + "class_getName", + "class_getSuperclass", + "class_isMetaClass", + "ivar_getOffset", + "method_getName", + "method_getTypeEncoding", + "objc_alloc", + "objc_allocateClassPair", + "objc_autorelease", + "objc_autoreleasePoolPush", + "objc_autoreleaseReturnValue", + "objc_getAssociatedObject", + "objc_getClass", + "objc_getProtocol", + "objc_lookUpClass", + "objc_registerClassPair", + "objc_retain", + "objc_retainAutoreleaseReturnValue", + "objc_retainBlock", + "objc_setAssociatedObject", + "objc_storeWeak", + "object_getClass", + "object_isClass", + "protocol_copyProtocolList", + "protocol_getMethodDescription", + "protocol_getName", + "sel_registerName", + + + // @objc Swift._ContiguousArrayStorage.count.getter : Swift.Int + "$ss23_ContiguousArrayStorageC5countSivgTo", + // @objc Swift.__SwiftDeferredNSArray.count.getter : Swift.Int + "$ss22__SwiftDeferredNSArrayC5countSivgTo", + // @objc Swift._ContiguousArrayStorage.objectAt(Swift.Int) -> Swift.Unmanaged + "$ss23_ContiguousArrayStorageC8objectAtys9UnmanagedVyyXlGSiFTo", + // @objc Swift.__SwiftNativeNSArrayWithContiguousStorage.objectAt(Swift.Int) -> Swift.Unmanaged + "$ss41__SwiftNativeNSArrayWithContiguousStorageC8objectAtys9UnmanagedVyyXlGSiFTo", + // @objc Swift.__EmptyDictionarySingleton.count.getter : Swift.Int + "$ss26__EmptyDictionarySingletonC5countSivgTo", + // @objc Swift._SwiftDeferredNSDictionary.count.getter : Swift.Int + "$ss26_SwiftDeferredNSDictionaryC5countSivgTo", + // @objc Swift._SwiftDeferredNSDictionary.keyEnumerator() -> Swift._NSEnumerator + "$ss26_SwiftDeferredNSDictionaryC13keyEnumerators13_NSEnumerator_pyFTo", + // @objc Swift._SwiftDictionaryNSEnumerator.nextObject() -> Swift.AnyObject? + "$ss28_SwiftDictionaryNSEnumeratorC10nextObjectyXlSgyFTo", + + "llvm.assume", + "llvm.ceil.*", + "llvm.copysign.*", + "llvm.cos.*", + "llvm.ctlz.*", + "llvm.ctpop.*", + "llvm.cttz.*", + "llvm.dbg.*", + "llvm.eh.typeid.for", + "llvm.exp.*", + "llvm.fabs.*", + "llvm.fabs.*", + "llvm.floor.*", + "llvm.instrprof.*", + "llvm.lifetime.*", + "llvm.log.*", + "llvm.log10.*", + "llvm.log2.*", + "llvm.memcpy.*", + "llvm.memmove.*", + "llvm.memset.*", + "llvm.objc.autoreleaseReturnValue", + "llvm.objc.retain", + "llvm.objectsize.*", + "llvm.pow.*", + "llvm.rint.*", + "llvm.sin.*", + "llvm.sqrt.*", + "llvm.umul.*", + "llvm.va_end", + "llvm.va_start", + "llvm.x86.avx2.*", + "llvm.x86.ssse3.*", + + "SetConsoleOutputCP", + "SetConsoleCP", + "QueryPerformanceCounter", + "VirtualAlloc", + "FlsSetValue", + "GetCurrentProcess", + "FlsFree", + "K32GetProcessMemoryInfo", +}; + +namespace { + +class KnownFunctionChecker { +public: + KnownFunctionChecker() { + for (int i = 0; i < Kotlin_callsCheckerKnownFunctionsCount; i++) { + known_functions_.insert(Kotlin_callsCheckerKnownFunctions[i]); + } + std::copy(std::begin(Kotlin_callsCheckerGoodFunctionNames), std::end(Kotlin_callsCheckerGoodFunctionNames), std::begin(good_names_copy_)); + std::sort(std::begin(good_names_copy_), std::end(good_names_copy_)); + } + + bool isKnown(const void* fun) const noexcept { + return known_functions_.find(fun) != known_functions_.end(); + } + + bool isSafeByName(std::string_view name) const noexcept { + auto it = std::lower_bound(std::begin(good_names_copy_), std::end(good_names_copy_), name); + auto check = [&](std::string_view banned) { + if (banned.back() != '*') { + return banned == name; + } + return name.substr(0, banned.size() - 1) == banned.substr(0, banned.size() - 1); + }; + if (it != std::end(good_names_copy_) && check(*it)) { + return true; + } + if (it != std::begin(good_names_copy_) && check(*std::prev(it))) { + return true; + } + return false; + } + + ~KnownFunctionChecker() = delete; + +private: + KStdUnorderedSet known_functions_; + std::string_view good_names_copy_[sizeof(Kotlin_callsCheckerGoodFunctionNames) / sizeof(Kotlin_callsCheckerGoodFunctionNames[0])]; +}; + +[[clang::no_destroy]] const KnownFunctionChecker checker; + + +constexpr int MSG_SEND_TO_NULL = -1; +constexpr int CALLED_LLVM_BUILTIN = -2; + +} + +/** + * This function calls is inserted to llvm bitcode automatically, so it can be called almost anywhre. + * + * Although, function itself is excluded, it can call itself indirectly, from other called functions. + * Because of this, thread_local guard is used to avoid recursive calls. + * + * Unfortunately, function can be called in thread constructors or destructors, where thread local data + * should not be accessed. So before guard checking we need to check is thread destructor is running, + * which requires special handling of recursive calls from this check. + */ +extern "C" RUNTIME_NOTHROW void Kotlin_mm_checkStateAtExternalFunctionCall(const char* caller, const char *callee, const void *calleePtr) noexcept { + if (reinterpret_cast(calleePtr) == MSG_SEND_TO_NULL) return; // objc_sendMsg called on nil, it does nothing, so it's ok + if (konan::isOnThreadExitNotSetOrAlreadyStarted()) return; + static thread_local bool recursiveCallGuard = false; + if (recursiveCallGuard) return; + if (!mm::ThreadRegistry::Instance().IsCurrentThreadRegistered()) return; + struct unlockGuard { + unlockGuard() { recursiveCallGuard = true; } + ~unlockGuard() { recursiveCallGuard = false; } + } guard; + + + auto actualState = GetThreadState(); + if (actualState == ThreadState::kNative) { + return; + } + if (reinterpret_cast(calleePtr) != CALLED_LLVM_BUILTIN && checker.isKnown(calleePtr)) { + return; + } + + char buf[200]; + if (callee == nullptr) { + if (AddressToSymbol(calleePtr, buf, sizeof(buf))) { + callee = buf; + } else { + callee = "unknown function"; + } + } + + if (checker.isSafeByName(callee)) { + return; + } + + RuntimeFail("Expected kNative thread state at call of function %s by function %s", callee, caller); +} + diff --git a/kotlin-native/runtime/src/mm/cpp/Memory.cpp b/kotlin-native/runtime/src/mm/cpp/Memory.cpp index 98c4f49fcd4..3108682f457 100644 --- a/kotlin-native/runtime/src/mm/cpp/Memory.cpp +++ b/kotlin-native/runtime/src/mm/cpp/Memory.cpp @@ -107,6 +107,9 @@ extern "C" void DeinitMemory(MemoryState* state, bool destroyRuntime) { // TODO: Also make sure that finalizers are run. } mm::ThreadRegistry::Instance().Unregister(node); + if (destroyRuntime) { + mm::ThreadRegistry::ClearCurrentThreadData(); + } } extern "C" void RestoreMemory(MemoryState*) { diff --git a/kotlin-native/runtime/src/mm/cpp/ThreadSuspension.cpp b/kotlin-native/runtime/src/mm/cpp/ThreadSuspension.cpp index cd2af06523c..1d3cc90c266 100644 --- a/kotlin-native/runtime/src/mm/cpp/ThreadSuspension.cpp +++ b/kotlin-native/runtime/src/mm/cpp/ThreadSuspension.cpp @@ -44,7 +44,7 @@ std::condition_variable gSuspendsionCondVar; } // namespace -bool kotlin::mm::ThreadSuspensionData::suspendIfRequested() noexcept { +NO_EXTERNAL_CALLS_CHECK bool kotlin::mm::ThreadSuspensionData::suspendIfRequested() noexcept { if (IsThreadSuspensionRequested()) { std::unique_lock lock(gSuspensionMutex); if (IsThreadSuspensionRequested()) {