[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
@@ -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()) {