[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.
This commit is contained in:
Pavel Kunyavskiy
2021-06-17 18:15:20 +03:00
committed by Space
parent 1ba04fdd21
commit fc75486611
11 changed files with 624 additions and 6 deletions
@@ -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<String>) {
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()
}
@@ -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)
@@ -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",
@@ -230,6 +230,16 @@ internal fun RuntimeAware.isObjectType(type: LLVMTypeRef): Boolean {
internal fun CArrayPointer<ByteVar>.getBytes(size: Long) =
(0 .. size-1).map { this[it] }.toByteArray()
internal fun LLVMValueRef.getAsCString() : String {
memScoped {
val lengthPtr = alloc<size_tVar>()
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)
}
@@ -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)
}
@@ -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
@@ -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) {
@@ -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;
}
@@ -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 <string_view>
#include <cstring>
#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<char>::~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<char, std::__1::char_traits<char>, std::__1::allocator<char> >::~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<char, std::char_traits<char>, std::allocator<char> >::~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<Swift.AnyObject>
"$ss23_ContiguousArrayStorageC8objectAtys9UnmanagedVyyXlGSiFTo",
// @objc Swift.__SwiftNativeNSArrayWithContiguousStorage.objectAt(Swift.Int) -> Swift.Unmanaged<Swift.AnyObject>
"$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<const void*> 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<int64_t>(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<int64_t>(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);
}
@@ -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*) {
@@ -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()) {