diff --git a/.gitmodules b/.gitmodules
index b79ccd467f895049602ca3947757d423b96f97c2..15d4b83af0efbd63e085498c5f48f525cb6898e5 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,3 @@
-[submodule "app/src/main/cpp/Dobby"]
-	path = app/src/main/cpp/Dobby
-	url = https://github.com/jmpews/Dobby.git
+[submodule "app/src/main/cpp/libcxx"]
+	path = app/src/main/cpp/libcxx
+	url = https://github.com/topjohnwu/libcxx.git
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 7d39699ab05949d8e54e4ad87ad7898a1b3f5f1d..c6a5fc3fee295f3a215d34b34a1b698e90e28618 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -8,16 +8,6 @@ android {
     ndkVersion = "26.1.10909125"
     buildToolsVersion = "34.0.0"
 
-    packaging {
-        jniLibs {
-            excludes += "**/libdobby.so"
-        }
-    }
-
-    buildFeatures {
-        prefab = true
-    }
-
     defaultConfig {
         applicationId = "es.chiteroman.playintegrityfix"
         minSdk = 26
@@ -26,15 +16,10 @@ android {
         versionName = "1.0"
 
         externalNativeBuild {
-            cmake {
-                arguments += "-DANDROID_STL=none"
-                arguments += "-DCMAKE_BUILD_TYPE=MinSizeRel"
-
-                cppFlags += "-std=c++20"
-                cppFlags += "-fno-exceptions"
-                cppFlags += "-fno-rtti"
-                cppFlags += "-fvisibility=hidden"
-                cppFlags += "-fvisibility-inlines-hidden"
+            ndk {
+                jobs = Runtime.getRuntime().availableProcessors()
+                abiFilters += "armeabi-v7a"
+                abiFilters += "arm64-v8a"
             }
         }
     }
@@ -53,13 +38,8 @@ android {
     }
 
     externalNativeBuild {
-        cmake {
-            path = file("src/main/cpp/CMakeLists.txt")
-            version = "3.22.1"
+        ndkBuild {
+            path = file("src/main/cpp/Android.mk")
         }
     }
-}
-
-dependencies {
-    implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0")
 }
\ No newline at end of file
diff --git a/app/src/main/cpp/Android.mk b/app/src/main/cpp/Android.mk
new file mode 100644
index 0000000000000000000000000000000000000000..9632627a8ec7c030098b14d15ff5100625760f6f
--- /dev/null
+++ b/app/src/main/cpp/Android.mk
@@ -0,0 +1,33 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := zygisk
+LOCAL_SRC_FILES := main.cpp
+LOCAL_C_INCLUDES := $(LOCAL_PATH)
+
+LOCAL_SRC_FILES += $(wildcard $(LOCAL_PATH)/shadowhook/*.c)
+LOCAL_SRC_FILES += $(wildcard $(LOCAL_PATH)/shadowhook/common/*.c)
+LOCAL_SRC_FILES += $(wildcard $(LOCAL_PATH)/shadowhook/third_party/xdl/*.c)
+
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/shadowhook
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/shadowhook/common
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/shadowhook/include
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/shadowhook/third_party/bsd
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/shadowhook/third_party/lss
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/shadowhook/third_party/xdl
+
+ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
+  LOCAL_SRC_FILES += $(wildcard $(LOCAL_PATH)/shadowhook/arch/arm/*.c)
+  LOCAL_C_INCLUDES += $(LOCAL_PATH)/shadowhook/arch/arm
+endif
+
+ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
+  LOCAL_SRC_FILES += $(wildcard $(LOCAL_PATH)/shadowhook/arch/arm64/*.c)
+  LOCAL_C_INCLUDES += $(LOCAL_PATH)/shadowhook/arch/arm64
+endif
+
+LOCAL_STATIC_LIBRARIES := libcxx
+LOCAL_LDLIBS := -llog
+include $(BUILD_SHARED_LIBRARY)
+
+include $(LOCAL_PATH)/libcxx/Android.mk
\ No newline at end of file
diff --git a/app/src/main/cpp/Application.mk b/app/src/main/cpp/Application.mk
new file mode 100644
index 0000000000000000000000000000000000000000..da73f7ceb22cdfd30d8f384aabbc1bbaf18260eb
--- /dev/null
+++ b/app/src/main/cpp/Application.mk
@@ -0,0 +1,4 @@
+APP_STL      := none
+APP_CFLAGS   := -Oz -fno-exceptions -fno-rtti -fvisibility=hidden -fvisibility-inlines-hidden
+APP_CPPFLAGS := -std=c++20
+APP_LDFLAGS  := -Oz
\ No newline at end of file
diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt
deleted file mode 100644
index 2071d710ca6dcc58db8e2596b19fef5aadafb498..0000000000000000000000000000000000000000
--- a/app/src/main/cpp/CMakeLists.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-cmake_minimum_required(VERSION 3.22.1)
-
-project("playintegrityfix")
-
-find_package(cxx REQUIRED CONFIG)
-
-link_libraries(cxx::cxx)
-
-add_library(${CMAKE_PROJECT_NAME} SHARED main.cpp)
-
-add_subdirectory(Dobby)
-
-SET_OPTION(Plugin.Android.BionicLinkerUtil ON)
-
-target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE log dobby_static)
\ No newline at end of file
diff --git a/app/src/main/cpp/Dobby b/app/src/main/cpp/Dobby
deleted file mode 160000
index b0176de574104726bb68dff3b77ee666300fc338..0000000000000000000000000000000000000000
--- a/app/src/main/cpp/Dobby
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit b0176de574104726bb68dff3b77ee666300fc338
diff --git a/app/src/main/cpp/json.hpp b/app/src/main/cpp/json.hpp
index dc58ca118954cdf1d27fee4578b504f855f699c3..4d1a37ad7cb874769d911fc3362d567da3aca291 100644
--- a/app/src/main/cpp/json.hpp
+++ b/app/src/main/cpp/json.hpp
@@ -27,6 +27,7 @@
 #endif  // JSON_NO_IO
 #include <iterator> // random_access_iterator_tag
 #include <memory> // unique_ptr
+#include <numeric> // accumulate
 #include <string> // string, stoi, to_string
 #include <utility> // declval, forward, move, pair, swap
 #include <vector> // vector
@@ -182,9 +183,6 @@
 
 #include <cstddef> // nullptr_t
 #include <exception> // exception
-#if JSON_DIAGNOSTICS
-    #include <numeric> // accumulate
-#endif
 #include <stdexcept> // runtime_error
 #include <string> // to_string
 #include <vector> // vector
@@ -2485,14 +2483,6 @@ JSON_HEDLEY_DIAGNOSTIC_POP
     #endif
 #endif
 
-#ifndef JSON_HAS_STATIC_RTTI
-    #if !defined(_HAS_STATIC_RTTI) || _HAS_STATIC_RTTI != 0
-        #define JSON_HAS_STATIC_RTTI 1
-    #else
-        #define JSON_HAS_STATIC_RTTI 0
-    #endif
-#endif
-
 #ifdef JSON_HAS_CPP_17
     #define JSON_INLINE_VARIABLE inline
 #else
@@ -2600,13 +2590,12 @@ JSON_HEDLEY_DIAGNOSTIC_POP
              class NumberUnsignedType, class NumberFloatType,              \
              template<typename> class AllocatorType,                       \
              template<typename, typename = void> class JSONSerializer,     \
-             class BinaryType,                                             \
-             class CustomBaseClass>
+             class BinaryType>
 
 #define NLOHMANN_BASIC_JSON_TPL                                            \
     basic_json<ObjectType, ArrayType, StringType, BooleanType,             \
     NumberIntegerType, NumberUnsignedType, NumberFloatType,                \
-    AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>
+    AllocatorType, JSONSerializer, BinaryType>
 
 // Macros to simplify conversion from/to types
 
@@ -2756,7 +2745,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP
 
 #define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...)  \
     friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
-    friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }
+    friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { Type nlohmann_json_default_obj; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }
 
 /*!
 @brief macro
@@ -2769,7 +2758,8 @@ JSON_HEDLEY_DIAGNOSTIC_POP
 
 #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...)  \
     inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
-    inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }
+    inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { Type nlohmann_json_default_obj; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }
+
 
 // inspired from https://stackoverflow.com/a/26745591
 // allows to call any std function as if (e.g. with begin):
@@ -3399,8 +3389,7 @@ NLOHMANN_JSON_NAMESPACE_END
     template<typename U> class AllocatorType = std::allocator,
     template<typename T, typename SFINAE = void> class JSONSerializer =
     adl_serializer,
-    class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError
-    class CustomBaseClass = void>
+    class BinaryType = std::vector<std::uint8_t>>
     class basic_json;
 
     /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
@@ -3624,6 +3613,7 @@ template <typename... Ts>
 struct is_default_constructible<const std::tuple<Ts...>>
             : conjunction<is_default_constructible<Ts>...> {};
 
+
 template <typename T, typename... Args>
 struct is_constructible : std::is_constructible<T, Args...> {};
 
@@ -3639,6 +3629,7 @@ struct is_constructible<std::tuple<Ts...>> : is_default_constructible<std::tuple
 template <typename... Ts>
 struct is_constructible<const std::tuple<Ts...>> : is_default_constructible<const std::tuple<Ts...>> {};
 
+
 template<typename T, typename = void>
 struct is_iterator_traits : std::false_type {};
 
@@ -4048,6 +4039,7 @@ struct value_in_range_of_impl2<OfType, T, false, true>
     }
 };
 
+
 template<typename OfType, typename T>
 struct value_in_range_of_impl2<OfType, T, true, true>
 {
@@ -4173,28 +4165,28 @@ inline std::size_t concat_length()
 }
 
 template<typename... Args>
-inline std::size_t concat_length(const char* cstr, const Args& ... rest);
+inline std::size_t concat_length(const char* cstr, Args&& ... rest);
 
 template<typename StringType, typename... Args>
-inline std::size_t concat_length(const StringType& str, const Args& ... rest);
+inline std::size_t concat_length(const StringType& str, Args&& ... rest);
 
 template<typename... Args>
-inline std::size_t concat_length(const char /*c*/, const Args& ... rest)
+inline std::size_t concat_length(const char /*c*/, Args&& ... rest)
 {
-    return 1 + concat_length(rest...);
+    return 1 + concat_length(std::forward<Args>(rest)...);
 }
 
 template<typename... Args>
-inline std::size_t concat_length(const char* cstr, const Args& ... rest)
+inline std::size_t concat_length(const char* cstr, Args&& ... rest)
 {
     // cppcheck-suppress ignoredReturnValue
-    return ::strlen(cstr) + concat_length(rest...);
+    return ::strlen(cstr) + concat_length(std::forward<Args>(rest)...);
 }
 
 template<typename StringType, typename... Args>
-inline std::size_t concat_length(const StringType& str, const Args& ... rest)
+inline std::size_t concat_length(const StringType& str, Args&& ... rest)
 {
-    return str.size() + concat_length(rest...);
+    return str.size() + concat_length(std::forward<Args>(rest)...);
 }
 
 template<typename OutStringType>
@@ -4285,7 +4277,7 @@ template<typename OutStringType = std::string, typename... Args>
 inline OutStringType concat(Args && ... args)
 {
     OutStringType str;
-    str.reserve(concat_length(args...));
+    str.reserve(concat_length(std::forward<Args>(args)...));
     concat_into(str, std::forward<Args>(args)...);
     return str;
 }
@@ -4294,6 +4286,7 @@ inline OutStringType concat(Args && ... args)
 NLOHMANN_JSON_NAMESPACE_END
 
 
+
 NLOHMANN_JSON_NAMESPACE_BEGIN
 namespace detail
 {
@@ -4341,9 +4334,9 @@ class exception : public std::exception
             {
                 case value_t::array:
                 {
-                    for (std::size_t i = 0; i < current->m_parent->m_data.m_value.array->size(); ++i)
+                    for (std::size_t i = 0; i < current->m_parent->m_value.array->size(); ++i)
                     {
-                        if (&current->m_parent->m_data.m_value.array->operator[](i) == current)
+                        if (&current->m_parent->m_value.array->operator[](i) == current)
                         {
                             tokens.emplace_back(std::to_string(i));
                             break;
@@ -4354,7 +4347,7 @@ class exception : public std::exception
 
                 case value_t::object:
                 {
-                    for (const auto& element : *current->m_parent->m_data.m_value.object)
+                    for (const auto& element : *current->m_parent->m_value.object)
                     {
                         if (&element.second == current)
                         {
@@ -4417,17 +4410,17 @@ class parse_error : public exception
     template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
     static parse_error create(int id_, const position_t& pos, const std::string& what_arg, BasicJsonContext context)
     {
-        const std::string w = concat(exception::name("parse_error", id_), "parse error",
-                                     position_string(pos), ": ", exception::diagnostics(context), what_arg);
+        std::string w = concat(exception::name("parse_error", id_), "parse error",
+                               position_string(pos), ": ", exception::diagnostics(context), what_arg);
         return {id_, pos.chars_read_total, w.c_str()};
     }
 
     template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
     static parse_error create(int id_, std::size_t byte_, const std::string& what_arg, BasicJsonContext context)
     {
-        const std::string w = concat(exception::name("parse_error", id_), "parse error",
-                                     (byte_ != 0 ? (concat(" at byte ", std::to_string(byte_))) : ""),
-                                     ": ", exception::diagnostics(context), what_arg);
+        std::string w = concat(exception::name("parse_error", id_), "parse error",
+                               (byte_ != 0 ? (concat(" at byte ", std::to_string(byte_))) : ""),
+                               ": ", exception::diagnostics(context), what_arg);
         return {id_, byte_, w.c_str()};
     }
 
@@ -4461,7 +4454,7 @@ class invalid_iterator : public exception
     template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
     static invalid_iterator create(int id_, const std::string& what_arg, BasicJsonContext context)
     {
-        const std::string w = concat(exception::name("invalid_iterator", id_), exception::diagnostics(context), what_arg);
+        std::string w = concat(exception::name("invalid_iterator", id_), exception::diagnostics(context), what_arg);
         return {id_, w.c_str()};
     }
 
@@ -4479,7 +4472,7 @@ class type_error : public exception
     template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
     static type_error create(int id_, const std::string& what_arg, BasicJsonContext context)
     {
-        const std::string w = concat(exception::name("type_error", id_), exception::diagnostics(context), what_arg);
+        std::string w = concat(exception::name("type_error", id_), exception::diagnostics(context), what_arg);
         return {id_, w.c_str()};
     }
 
@@ -4496,7 +4489,7 @@ class out_of_range : public exception
     template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
     static out_of_range create(int id_, const std::string& what_arg, BasicJsonContext context)
     {
-        const std::string w = concat(exception::name("out_of_range", id_), exception::diagnostics(context), what_arg);
+        std::string w = concat(exception::name("out_of_range", id_), exception::diagnostics(context), what_arg);
         return {id_, w.c_str()};
     }
 
@@ -4513,7 +4506,7 @@ class other_error : public exception
     template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
     static other_error create(int id_, const std::string& what_arg, BasicJsonContext context)
     {
-        const std::string w = concat(exception::name("other_error", id_), exception::diagnostics(context), what_arg);
+        std::string w = concat(exception::name("other_error", id_), exception::diagnostics(context), what_arg);
         return {id_, w.c_str()};
     }
 
@@ -5154,10 +5147,10 @@ template<typename IteratorType> class iteration_proxy_value
     // older GCCs are a bit fussy and require explicit noexcept specifiers on defaulted functions
     iteration_proxy_value(iteration_proxy_value&&)
     noexcept(std::is_nothrow_move_constructible<IteratorType>::value
-             && std::is_nothrow_move_constructible<string_type>::value) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor,cppcoreguidelines-noexcept-move-operations)
+             && std::is_nothrow_move_constructible<string_type>::value) = default;
     iteration_proxy_value& operator=(iteration_proxy_value&&)
     noexcept(std::is_nothrow_move_assignable<IteratorType>::value
-             && std::is_nothrow_move_assignable<string_type>::value) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor,cppcoreguidelines-noexcept-move-operations)
+             && std::is_nothrow_move_assignable<string_type>::value) = default;
     ~iteration_proxy_value() = default;
 
     /// dereference operator (needed for range-based for)
@@ -5304,11 +5297,11 @@ namespace std
     #pragma clang diagnostic ignored "-Wmismatched-tags"
 #endif
 template<typename IteratorType>
-class tuple_size<::nlohmann::detail::iteration_proxy_value<IteratorType>> // NOLINT(cert-dcl58-cpp)
+class tuple_size<::nlohmann::detail::iteration_proxy_value<IteratorType>>
             : public std::integral_constant<std::size_t, 2> {};
 
 template<std::size_t N, typename IteratorType>
-class tuple_element<N, ::nlohmann::detail::iteration_proxy_value<IteratorType >> // NOLINT(cert-dcl58-cpp)
+class tuple_element<N, ::nlohmann::detail::iteration_proxy_value<IteratorType >>
 {
   public:
     using type = decltype(
@@ -5347,7 +5340,7 @@ namespace detail
 
 /*
  * Note all external_constructor<>::construct functions need to call
- * j.m_data.m_value.destroy(j.m_data.m_type) to avoid a memory leak in case j contains an
+ * j.m_value.destroy(j.m_type) to avoid a memory leak in case j contains an
  * allocated value (e.g., a string). See bug issue
  * https://github.com/nlohmann/json/issues/2865 for more information.
  */
@@ -5360,9 +5353,9 @@ struct external_constructor<value_t::boolean>
     template<typename BasicJsonType>
     static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept
     {
-        j.m_data.m_value.destroy(j.m_data.m_type);
-        j.m_data.m_type = value_t::boolean;
-        j.m_data.m_value = b;
+        j.m_value.destroy(j.m_type);
+        j.m_type = value_t::boolean;
+        j.m_value = b;
         j.assert_invariant();
     }
 };
@@ -5373,18 +5366,18 @@ struct external_constructor<value_t::string>
     template<typename BasicJsonType>
     static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s)
     {
-        j.m_data.m_value.destroy(j.m_data.m_type);
-        j.m_data.m_type = value_t::string;
-        j.m_data.m_value = s;
+        j.m_value.destroy(j.m_type);
+        j.m_type = value_t::string;
+        j.m_value = s;
         j.assert_invariant();
     }
 
     template<typename BasicJsonType>
     static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s)
     {
-        j.m_data.m_value.destroy(j.m_data.m_type);
-        j.m_data.m_type = value_t::string;
-        j.m_data.m_value = std::move(s);
+        j.m_value.destroy(j.m_type);
+        j.m_type = value_t::string;
+        j.m_value = std::move(s);
         j.assert_invariant();
     }
 
@@ -5393,9 +5386,9 @@ struct external_constructor<value_t::string>
                              int > = 0 >
     static void construct(BasicJsonType& j, const CompatibleStringType& str)
     {
-        j.m_data.m_value.destroy(j.m_data.m_type);
-        j.m_data.m_type = value_t::string;
-        j.m_data.m_value.string = j.template create<typename BasicJsonType::string_t>(str);
+        j.m_value.destroy(j.m_type);
+        j.m_type = value_t::string;
+        j.m_value.string = j.template create<typename BasicJsonType::string_t>(str);
         j.assert_invariant();
     }
 };
@@ -5406,18 +5399,18 @@ struct external_constructor<value_t::binary>
     template<typename BasicJsonType>
     static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b)
     {
-        j.m_data.m_value.destroy(j.m_data.m_type);
-        j.m_data.m_type = value_t::binary;
-        j.m_data.m_value = typename BasicJsonType::binary_t(b);
+        j.m_value.destroy(j.m_type);
+        j.m_type = value_t::binary;
+        j.m_value = typename BasicJsonType::binary_t(b);
         j.assert_invariant();
     }
 
     template<typename BasicJsonType>
     static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b)
     {
-        j.m_data.m_value.destroy(j.m_data.m_type);
-        j.m_data.m_type = value_t::binary;
-        j.m_data.m_value = typename BasicJsonType::binary_t(std::move(b));
+        j.m_value.destroy(j.m_type);
+        j.m_type = value_t::binary;
+        j.m_value = typename BasicJsonType::binary_t(std::move(b));
         j.assert_invariant();
     }
 };
@@ -5428,9 +5421,9 @@ struct external_constructor<value_t::number_float>
     template<typename BasicJsonType>
     static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept
     {
-        j.m_data.m_value.destroy(j.m_data.m_type);
-        j.m_data.m_type = value_t::number_float;
-        j.m_data.m_value = val;
+        j.m_value.destroy(j.m_type);
+        j.m_type = value_t::number_float;
+        j.m_value = val;
         j.assert_invariant();
     }
 };
@@ -5441,9 +5434,9 @@ struct external_constructor<value_t::number_unsigned>
     template<typename BasicJsonType>
     static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept
     {
-        j.m_data.m_value.destroy(j.m_data.m_type);
-        j.m_data.m_type = value_t::number_unsigned;
-        j.m_data.m_value = val;
+        j.m_value.destroy(j.m_type);
+        j.m_type = value_t::number_unsigned;
+        j.m_value = val;
         j.assert_invariant();
     }
 };
@@ -5454,9 +5447,9 @@ struct external_constructor<value_t::number_integer>
     template<typename BasicJsonType>
     static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept
     {
-        j.m_data.m_value.destroy(j.m_data.m_type);
-        j.m_data.m_type = value_t::number_integer;
-        j.m_data.m_value = val;
+        j.m_value.destroy(j.m_type);
+        j.m_type = value_t::number_integer;
+        j.m_value = val;
         j.assert_invariant();
     }
 };
@@ -5467,9 +5460,9 @@ struct external_constructor<value_t::array>
     template<typename BasicJsonType>
     static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr)
     {
-        j.m_data.m_value.destroy(j.m_data.m_type);
-        j.m_data.m_type = value_t::array;
-        j.m_data.m_value = arr;
+        j.m_value.destroy(j.m_type);
+        j.m_type = value_t::array;
+        j.m_value = arr;
         j.set_parents();
         j.assert_invariant();
     }
@@ -5477,9 +5470,9 @@ struct external_constructor<value_t::array>
     template<typename BasicJsonType>
     static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr)
     {
-        j.m_data.m_value.destroy(j.m_data.m_type);
-        j.m_data.m_type = value_t::array;
-        j.m_data.m_value = std::move(arr);
+        j.m_value.destroy(j.m_type);
+        j.m_type = value_t::array;
+        j.m_value = std::move(arr);
         j.set_parents();
         j.assert_invariant();
     }
@@ -5492,9 +5485,9 @@ struct external_constructor<value_t::array>
         using std::begin;
         using std::end;
 
-        j.m_data.m_value.destroy(j.m_data.m_type);
-        j.m_data.m_type = value_t::array;
-        j.m_data.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr));
+        j.m_value.destroy(j.m_type);
+        j.m_type = value_t::array;
+        j.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr));
         j.set_parents();
         j.assert_invariant();
     }
@@ -5502,14 +5495,14 @@ struct external_constructor<value_t::array>
     template<typename BasicJsonType>
     static void construct(BasicJsonType& j, const std::vector<bool>& arr)
     {
-        j.m_data.m_value.destroy(j.m_data.m_type);
-        j.m_data.m_type = value_t::array;
-        j.m_data.m_value = value_t::array;
-        j.m_data.m_value.array->reserve(arr.size());
+        j.m_value.destroy(j.m_type);
+        j.m_type = value_t::array;
+        j.m_value = value_t::array;
+        j.m_value.array->reserve(arr.size());
         for (const bool x : arr)
         {
-            j.m_data.m_value.array->push_back(x);
-            j.set_parent(j.m_data.m_value.array->back());
+            j.m_value.array->push_back(x);
+            j.set_parent(j.m_value.array->back());
         }
         j.assert_invariant();
     }
@@ -5518,13 +5511,13 @@ struct external_constructor<value_t::array>
              enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
     static void construct(BasicJsonType& j, const std::valarray<T>& arr)
     {
-        j.m_data.m_value.destroy(j.m_data.m_type);
-        j.m_data.m_type = value_t::array;
-        j.m_data.m_value = value_t::array;
-        j.m_data.m_value.array->resize(arr.size());
+        j.m_value.destroy(j.m_type);
+        j.m_type = value_t::array;
+        j.m_value = value_t::array;
+        j.m_value.array->resize(arr.size());
         if (arr.size() > 0)
         {
-            std::copy(std::begin(arr), std::end(arr), j.m_data.m_value.array->begin());
+            std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin());
         }
         j.set_parents();
         j.assert_invariant();
@@ -5537,9 +5530,9 @@ struct external_constructor<value_t::object>
     template<typename BasicJsonType>
     static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj)
     {
-        j.m_data.m_value.destroy(j.m_data.m_type);
-        j.m_data.m_type = value_t::object;
-        j.m_data.m_value = obj;
+        j.m_value.destroy(j.m_type);
+        j.m_type = value_t::object;
+        j.m_value = obj;
         j.set_parents();
         j.assert_invariant();
     }
@@ -5547,9 +5540,9 @@ struct external_constructor<value_t::object>
     template<typename BasicJsonType>
     static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
     {
-        j.m_data.m_value.destroy(j.m_data.m_type);
-        j.m_data.m_type = value_t::object;
-        j.m_data.m_value = std::move(obj);
+        j.m_value.destroy(j.m_type);
+        j.m_type = value_t::object;
+        j.m_value = std::move(obj);
         j.set_parents();
         j.assert_invariant();
     }
@@ -5561,9 +5554,9 @@ struct external_constructor<value_t::object>
         using std::begin;
         using std::end;
 
-        j.m_data.m_value.destroy(j.m_data.m_type);
-        j.m_data.m_type = value_t::object;
-        j.m_data.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj));
+        j.m_value.destroy(j.m_type);
+        j.m_type = value_t::object;
+        j.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj));
         j.set_parents();
         j.assert_invariant();
     }
@@ -6147,6 +6140,7 @@ class file_input_adapter
     std::FILE* m_file;
 };
 
+
 /*!
 Input adapter for a (caching) istream. Ignores a UFT Byte Order Mark at
 beginning of input. Does not support changing the underlying std::streambuf
@@ -6245,6 +6239,7 @@ class iterator_input_adapter
     }
 };
 
+
 template<typename BaseInputAdapter, size_t T>
 struct wide_string_input_helper;
 
@@ -6368,7 +6363,7 @@ struct wide_string_input_helper<BaseInputAdapter, 2>
     }
 };
 
-// Wraps another input adapter to convert wide character types into individual bytes.
+// Wraps another input apdater to convert wide character types into individual bytes.
 template<typename BaseInputAdapter, typename WideCharType>
 class wide_string_input_adapter
 {
@@ -6413,6 +6408,7 @@ class wide_string_input_adapter
     std::size_t utf8_bytes_filled = 0;
 };
 
+
 template<typename IteratorType, typename Enable = void>
 struct iterator_input_adapter_factory
 {
@@ -6714,6 +6710,7 @@ struct json_sax
     virtual ~json_sax() = default;
 };
 
+
 namespace detail
 {
 /*!
@@ -6815,7 +6812,7 @@ class json_sax_dom_parser
         JSON_ASSERT(ref_stack.back()->is_object());
 
         // add null at given key and store the reference for later
-        object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val));
+        object_element = &(ref_stack.back()->m_value.object->operator[](val));
         return true;
     }
 
@@ -6890,8 +6887,8 @@ class json_sax_dom_parser
 
         if (ref_stack.back()->is_array())
         {
-            ref_stack.back()->m_data.m_value.array->emplace_back(std::forward<Value>(v));
-            return &(ref_stack.back()->m_data.m_value.array->back());
+            ref_stack.back()->m_value.array->emplace_back(std::forward<Value>(v));
+            return &(ref_stack.back()->m_value.array->back());
         }
 
         JSON_ASSERT(ref_stack.back()->is_object());
@@ -7010,7 +7007,7 @@ class json_sax_dom_callback_parser
         // add discarded value at given key and store the reference for later
         if (keep && ref_stack.back())
         {
-            object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val) = discarded);
+            object_element = &(ref_stack.back()->m_value.object->operator[](val) = discarded);
         }
 
         return true;
@@ -7095,7 +7092,7 @@ class json_sax_dom_callback_parser
         // remove discarded value
         if (!keep && !ref_stack.empty() && ref_stack.back()->is_array())
         {
-            ref_stack.back()->m_data.m_value.array->pop_back();
+            ref_stack.back()->m_value.array->pop_back();
         }
 
         return true;
@@ -7162,7 +7159,7 @@ class json_sax_dom_callback_parser
         if (ref_stack.empty())
         {
             root = std::move(value);
-            return {true, & root};
+            return {true, &root};
         }
 
         // skip this value if we already decided to skip the parent
@@ -7178,8 +7175,8 @@ class json_sax_dom_callback_parser
         // array
         if (ref_stack.back()->is_array())
         {
-            ref_stack.back()->m_data.m_value.array->emplace_back(std::move(value));
-            return {true, & (ref_stack.back()->m_data.m_value.array->back())};
+            ref_stack.back()->m_value.array->emplace_back(std::move(value));
+            return {true, &(ref_stack.back()->m_value.array->back())};
         }
 
         // object
@@ -7526,7 +7523,7 @@ class lexer : public lexer_base<BasicJsonType>
         for (auto range = ranges.begin(); range != ranges.end(); ++range)
         {
             get();
-            if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) // NOLINT(bugprone-inc-dec-in-conditions)
+            if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range)))
             {
                 add(current);
             }
@@ -9132,6 +9129,7 @@ static inline bool little_endianness(int num = 1) noexcept
     return *reinterpret_cast<char*>(&num) == 1;
 }
 
+
 ///////////////////
 // binary reader //
 ///////////////////
@@ -9406,7 +9404,7 @@ class binary_reader
             {
                 std::array<char, 3> cr{{}};
                 static_cast<void>((std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast<unsigned char>(element_type))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
-                const std::string cr_str{cr.data()};
+                std::string cr_str{cr.data()};
                 return sax->parse_error(element_type_parse_position, cr_str,
                                         parse_error::create(114, element_type_parse_position, concat("Unsupported BSON record type 0x", cr_str), nullptr));
             }
@@ -11229,7 +11227,7 @@ class binary_reader
                 }
                 if (is_ndarray) // ndarray dimensional vector can only contain integers, and can not embed another array
                 {
-                    return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message(input_format, "ndarray dimensional vector is not allowed", "size"), nullptr));
+                    return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message(input_format, "ndarray dimentional vector is not allowed", "size"), nullptr));
                 }
                 std::vector<size_t> dim;
                 if (JSON_HEDLEY_UNLIKELY(!get_ubjson_ndarray_size(dim)))
@@ -11341,7 +11339,7 @@ class binary_reader
                                         exception_message(input_format, concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr));
             }
 
-            const bool is_error = get_ubjson_size_value(result.first, is_ndarray);
+            bool is_error = get_ubjson_size_value(result.first, is_ndarray);
             if (input_format == input_format_t::bjdata && is_ndarray)
             {
                 if (inside_ndarray)
@@ -11356,7 +11354,7 @@ class binary_reader
 
         if (current == '#')
         {
-            const bool is_error = get_ubjson_size_value(result.first, is_ndarray);
+            bool is_error = get_ubjson_size_value(result.first, is_ndarray);
             if (input_format == input_format_t::bjdata && is_ndarray)
             {
                 return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read,
@@ -12441,25 +12439,13 @@ class parser
                                                 m_lexer.get_token_string(),
                                                 parse_error::create(101, m_lexer.get_position(), exception_message(token_type::uninitialized, "value"), nullptr));
                     }
-                    case token_type::end_of_input:
-                    {
-                        if (JSON_HEDLEY_UNLIKELY(m_lexer.get_position().chars_read_total == 1))
-                        {
-                            return sax->parse_error(m_lexer.get_position(),
-                                                    m_lexer.get_token_string(),
-                                                    parse_error::create(101, m_lexer.get_position(),
-                                                            "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
-                        }
 
-                        return sax->parse_error(m_lexer.get_position(),
-                                                m_lexer.get_token_string(),
-                                                parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr));
-                    }
                     case token_type::uninitialized:
                     case token_type::end_array:
                     case token_type::end_object:
                     case token_type::name_separator:
                     case token_type::value_separator:
+                    case token_type::end_of_input:
                     case token_type::literal_or_value:
                     default: // the last token was unexpected
                     {
@@ -12901,7 +12887,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
     {
         JSON_ASSERT(m_object != nullptr);
 
-        switch (m_object->m_data.m_type)
+        switch (m_object->m_type)
         {
             case value_t::object:
             {
@@ -12998,17 +12984,17 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
     {
         JSON_ASSERT(m_object != nullptr);
 
-        switch (m_object->m_data.m_type)
+        switch (m_object->m_type)
         {
             case value_t::object:
             {
-                m_it.object_iterator = m_object->m_data.m_value.object->begin();
+                m_it.object_iterator = m_object->m_value.object->begin();
                 break;
             }
 
             case value_t::array:
             {
-                m_it.array_iterator = m_object->m_data.m_value.array->begin();
+                m_it.array_iterator = m_object->m_value.array->begin();
                 break;
             }
 
@@ -13042,17 +13028,17 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
     {
         JSON_ASSERT(m_object != nullptr);
 
-        switch (m_object->m_data.m_type)
+        switch (m_object->m_type)
         {
             case value_t::object:
             {
-                m_it.object_iterator = m_object->m_data.m_value.object->end();
+                m_it.object_iterator = m_object->m_value.object->end();
                 break;
             }
 
             case value_t::array:
             {
-                m_it.array_iterator = m_object->m_data.m_value.array->end();
+                m_it.array_iterator = m_object->m_value.array->end();
                 break;
             }
 
@@ -13081,17 +13067,17 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
     {
         JSON_ASSERT(m_object != nullptr);
 
-        switch (m_object->m_data.m_type)
+        switch (m_object->m_type)
         {
             case value_t::object:
             {
-                JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
+                JSON_ASSERT(m_it.object_iterator != m_object->m_value.object->end());
                 return m_it.object_iterator->second;
             }
 
             case value_t::array:
             {
-                JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
+                JSON_ASSERT(m_it.array_iterator != m_object->m_value.array->end());
                 return *m_it.array_iterator;
             }
 
@@ -13125,17 +13111,17 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
     {
         JSON_ASSERT(m_object != nullptr);
 
-        switch (m_object->m_data.m_type)
+        switch (m_object->m_type)
         {
             case value_t::object:
             {
-                JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
+                JSON_ASSERT(m_it.object_iterator != m_object->m_value.object->end());
                 return &(m_it.object_iterator->second);
             }
 
             case value_t::array:
             {
-                JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
+                JSON_ASSERT(m_it.array_iterator != m_object->m_value.array->end());
                 return &*m_it.array_iterator;
             }
 
@@ -13178,7 +13164,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
     {
         JSON_ASSERT(m_object != nullptr);
 
-        switch (m_object->m_data.m_type)
+        switch (m_object->m_type)
         {
             case value_t::object:
             {
@@ -13229,7 +13215,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
     {
         JSON_ASSERT(m_object != nullptr);
 
-        switch (m_object->m_data.m_type)
+        switch (m_object->m_type)
         {
             case value_t::object:
             {
@@ -13276,7 +13262,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
 
         JSON_ASSERT(m_object != nullptr);
 
-        switch (m_object->m_data.m_type)
+        switch (m_object->m_type)
         {
             case value_t::object:
                 return (m_it.object_iterator == other.m_it.object_iterator);
@@ -13321,7 +13307,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
 
         JSON_ASSERT(m_object != nullptr);
 
-        switch (m_object->m_data.m_type)
+        switch (m_object->m_type)
         {
             case value_t::object:
                 JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators", m_object));
@@ -13377,7 +13363,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
     {
         JSON_ASSERT(m_object != nullptr);
 
-        switch (m_object->m_data.m_type)
+        switch (m_object->m_type)
         {
             case value_t::object:
                 JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators", m_object));
@@ -13456,7 +13442,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
     {
         JSON_ASSERT(m_object != nullptr);
 
-        switch (m_object->m_data.m_type)
+        switch (m_object->m_type)
         {
             case value_t::object:
                 JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators", m_object));
@@ -13485,7 +13471,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
     {
         JSON_ASSERT(m_object != nullptr);
 
-        switch (m_object->m_data.m_type)
+        switch (m_object->m_type)
         {
             case value_t::object:
                 JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators", m_object));
@@ -13687,40 +13673,6 @@ NLOHMANN_JSON_NAMESPACE_END
 
 // #include <nlohmann/detail/iterators/primitive_iterator.hpp>
 
-// #include <nlohmann/detail/json_custom_base_class.hpp>
-
-
-#include <type_traits> // conditional, is_same
-
-// #include <nlohmann/detail/abi_macros.hpp>
-
-
-NLOHMANN_JSON_NAMESPACE_BEGIN
-namespace detail
-{
-
-/*!
-@brief Default base class of the @ref basic_json class.
-
-So that the correct implementations of the copy / move ctors / assign operators
-of @ref basic_json do not require complex case distinctions
-(no base class / custom base class used as customization point),
-@ref basic_json always has a base class.
-By default, this class is used because it is empty and thus has no effect
-on the behavior of @ref basic_json.
-*/
-struct json_default_base {};
-
-template<class T>
-using json_base_class = typename std::conditional <
-                        std::is_same<T, void>::value,
-                        json_default_base,
-                        T
-                        >::type;
-
-}  // namespace detail
-NLOHMANN_JSON_NAMESPACE_END
-
 // #include <nlohmann/detail/json_pointer.hpp>
 //     __ _____ _____ _____
 //  __|  |   __|     |   | |  JSON for Modern C++
@@ -13959,7 +13911,7 @@ class json_pointer
         const char* p = s.c_str();
         char* p_end = nullptr;
         errno = 0; // strtoull doesn't reset errno
-        const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int)
+        unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int)
         if (p == p_end // invalid input or empty string
                 || errno == ERANGE // out of range
                 || JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(p_end - p) != s.size())) // incomplete read
@@ -14115,7 +14067,7 @@ class json_pointer
                     if (reference_token == "-")
                     {
                         // explicitly treat "-" as index beyond the end
-                        ptr = &ptr->operator[](ptr->m_data.m_value.array->size());
+                        ptr = &ptr->operator[](ptr->m_value.array->size());
                     }
                     else
                     {
@@ -14167,7 +14119,7 @@ class json_pointer
                     {
                         // "-" always fails the range check
                         JSON_THROW(detail::out_of_range::create(402, detail::concat(
-                                "array index '-' (", std::to_string(ptr->m_data.m_value.array->size()),
+                                "array index '-' (", std::to_string(ptr->m_value.array->size()),
                                 ") is out of range"), ptr));
                     }
 
@@ -14224,7 +14176,7 @@ class json_pointer
                     if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
                     {
                         // "-" cannot be used for const access
-                        JSON_THROW(detail::out_of_range::create(402, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr));
+                        JSON_THROW(detail::out_of_range::create(402, detail::concat("array index '-' (", std::to_string(ptr->m_value.array->size()), ") is out of range"), ptr));
                     }
 
                     // use unchecked array access
@@ -14274,7 +14226,7 @@ class json_pointer
                     {
                         // "-" always fails the range check
                         JSON_THROW(detail::out_of_range::create(402, detail::concat(
-                                "array index '-' (", std::to_string(ptr->m_data.m_value.array->size()),
+                                "array index '-' (", std::to_string(ptr->m_value.array->size()),
                                 ") is out of range"), ptr));
                     }
 
@@ -14469,7 +14421,7 @@ class json_pointer
         {
             case detail::value_t::array:
             {
-                if (value.m_data.m_value.array->empty())
+                if (value.m_value.array->empty())
                 {
                     // flatten empty array as null
                     result[reference_string] = nullptr;
@@ -14477,10 +14429,10 @@ class json_pointer
                 else
                 {
                     // iterate array and use index as reference string
-                    for (std::size_t i = 0; i < value.m_data.m_value.array->size(); ++i)
+                    for (std::size_t i = 0; i < value.m_value.array->size(); ++i)
                     {
                         flatten(detail::concat(reference_string, '/', std::to_string(i)),
-                                value.m_data.m_value.array->operator[](i), result);
+                                value.m_value.array->operator[](i), result);
                     }
                 }
                 break;
@@ -14488,7 +14440,7 @@ class json_pointer
 
             case detail::value_t::object:
             {
-                if (value.m_data.m_value.object->empty())
+                if (value.m_value.object->empty())
                 {
                     // flatten empty object as null
                     result[reference_string] = nullptr;
@@ -14496,7 +14448,7 @@ class json_pointer
                 else
                 {
                     // iterate object and use keys as reference string
-                    for (const auto& element : *value.m_data.m_value.object)
+                    for (const auto& element : *value.m_value.object)
                     {
                         flatten(detail::concat(reference_string, '/', detail::escape(element.first)), element.second, result);
                     }
@@ -14543,7 +14495,7 @@ class json_pointer
         BasicJsonType result;
 
         // iterate the JSON object values
-        for (const auto& element : *value.m_data.m_value.object)
+        for (const auto& element : *value.m_value.object)
         {
             if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive()))
             {
@@ -15026,7 +14978,7 @@ class binary_writer
         {
             case value_t::object:
             {
-                write_bson_object(*j.m_data.m_value.object);
+                write_bson_object(*j.m_value.object);
                 break;
             }
 
@@ -15061,7 +15013,7 @@ class binary_writer
 
             case value_t::boolean:
             {
-                oa->write_character(j.m_data.m_value.boolean
+                oa->write_character(j.m_value.boolean
                                     ? to_char_type(0xF5)
                                     : to_char_type(0xF4));
                 break;
@@ -15069,42 +15021,42 @@ class binary_writer
 
             case value_t::number_integer:
             {
-                if (j.m_data.m_value.number_integer >= 0)
+                if (j.m_value.number_integer >= 0)
                 {
                     // CBOR does not differentiate between positive signed
                     // integers and unsigned integers. Therefore, we used the
                     // code from the value_t::number_unsigned case here.
-                    if (j.m_data.m_value.number_integer <= 0x17)
+                    if (j.m_value.number_integer <= 0x17)
                     {
-                        write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));
+                        write_number(static_cast<std::uint8_t>(j.m_value.number_integer));
                     }
-                    else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint8_t>::max)())
+                    else if (j.m_value.number_integer <= (std::numeric_limits<std::uint8_t>::max)())
                     {
                         oa->write_character(to_char_type(0x18));
-                        write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));
+                        write_number(static_cast<std::uint8_t>(j.m_value.number_integer));
                     }
-                    else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint16_t>::max)())
+                    else if (j.m_value.number_integer <= (std::numeric_limits<std::uint16_t>::max)())
                     {
                         oa->write_character(to_char_type(0x19));
-                        write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_integer));
+                        write_number(static_cast<std::uint16_t>(j.m_value.number_integer));
                     }
-                    else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint32_t>::max)())
+                    else if (j.m_value.number_integer <= (std::numeric_limits<std::uint32_t>::max)())
                     {
                         oa->write_character(to_char_type(0x1A));
-                        write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));
+                        write_number(static_cast<std::uint32_t>(j.m_value.number_integer));
                     }
                     else
                     {
                         oa->write_character(to_char_type(0x1B));
-                        write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
+                        write_number(static_cast<std::uint64_t>(j.m_value.number_integer));
                     }
                 }
                 else
                 {
                     // The conversions below encode the sign in the first
                     // byte, and the value is converted to a positive number.
-                    const auto positive_number = -1 - j.m_data.m_value.number_integer;
-                    if (j.m_data.m_value.number_integer >= -24)
+                    const auto positive_number = -1 - j.m_value.number_integer;
+                    if (j.m_value.number_integer >= -24)
                     {
                         write_number(static_cast<std::uint8_t>(0x20 + positive_number));
                     }
@@ -15134,52 +15086,52 @@ class binary_writer
 
             case value_t::number_unsigned:
             {
-                if (j.m_data.m_value.number_unsigned <= 0x17)
+                if (j.m_value.number_unsigned <= 0x17)
                 {
-                    write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_unsigned));
+                    write_number(static_cast<std::uint8_t>(j.m_value.number_unsigned));
                 }
-                else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint8_t>::max)())
+                else if (j.m_value.number_unsigned <= (std::numeric_limits<std::uint8_t>::max)())
                 {
                     oa->write_character(to_char_type(0x18));
-                    write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_unsigned));
+                    write_number(static_cast<std::uint8_t>(j.m_value.number_unsigned));
                 }
-                else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint16_t>::max)())
+                else if (j.m_value.number_unsigned <= (std::numeric_limits<std::uint16_t>::max)())
                 {
                     oa->write_character(to_char_type(0x19));
-                    write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_unsigned));
+                    write_number(static_cast<std::uint16_t>(j.m_value.number_unsigned));
                 }
-                else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint32_t>::max)())
+                else if (j.m_value.number_unsigned <= (std::numeric_limits<std::uint32_t>::max)())
                 {
                     oa->write_character(to_char_type(0x1A));
-                    write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_unsigned));
+                    write_number(static_cast<std::uint32_t>(j.m_value.number_unsigned));
                 }
                 else
                 {
                     oa->write_character(to_char_type(0x1B));
-                    write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
+                    write_number(static_cast<std::uint64_t>(j.m_value.number_unsigned));
                 }
                 break;
             }
 
             case value_t::number_float:
             {
-                if (std::isnan(j.m_data.m_value.number_float))
+                if (std::isnan(j.m_value.number_float))
                 {
                     // NaN is 0xf97e00 in CBOR
                     oa->write_character(to_char_type(0xF9));
                     oa->write_character(to_char_type(0x7E));
                     oa->write_character(to_char_type(0x00));
                 }
-                else if (std::isinf(j.m_data.m_value.number_float))
+                else if (std::isinf(j.m_value.number_float))
                 {
                     // Infinity is 0xf97c00, -Infinity is 0xf9fc00
                     oa->write_character(to_char_type(0xf9));
-                    oa->write_character(j.m_data.m_value.number_float > 0 ? to_char_type(0x7C) : to_char_type(0xFC));
+                    oa->write_character(j.m_value.number_float > 0 ? to_char_type(0x7C) : to_char_type(0xFC));
                     oa->write_character(to_char_type(0x00));
                 }
                 else
                 {
-                    write_compact_float(j.m_data.m_value.number_float, detail::input_format_t::cbor);
+                    write_compact_float(j.m_value.number_float, detail::input_format_t::cbor);
                 }
                 break;
             }
@@ -15187,7 +15139,7 @@ class binary_writer
             case value_t::string:
             {
                 // step 1: write control byte and the string length
-                const auto N = j.m_data.m_value.string->size();
+                const auto N = j.m_value.string->size();
                 if (N <= 0x17)
                 {
                     write_number(static_cast<std::uint8_t>(0x60 + N));
@@ -15217,15 +15169,15 @@ class binary_writer
 
                 // step 2: write the string
                 oa->write_characters(
-                    reinterpret_cast<const CharType*>(j.m_data.m_value.string->c_str()),
-                    j.m_data.m_value.string->size());
+                    reinterpret_cast<const CharType*>(j.m_value.string->c_str()),
+                    j.m_value.string->size());
                 break;
             }
 
             case value_t::array:
             {
                 // step 1: write control byte and the array size
-                const auto N = j.m_data.m_value.array->size();
+                const auto N = j.m_value.array->size();
                 if (N <= 0x17)
                 {
                     write_number(static_cast<std::uint8_t>(0x80 + N));
@@ -15254,7 +15206,7 @@ class binary_writer
                 // LCOV_EXCL_STOP
 
                 // step 2: write each element
-                for (const auto& el : *j.m_data.m_value.array)
+                for (const auto& el : *j.m_value.array)
                 {
                     write_cbor(el);
                 }
@@ -15263,32 +15215,32 @@ class binary_writer
 
             case value_t::binary:
             {
-                if (j.m_data.m_value.binary->has_subtype())
+                if (j.m_value.binary->has_subtype())
                 {
-                    if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits<std::uint8_t>::max)())
+                    if (j.m_value.binary->subtype() <= (std::numeric_limits<std::uint8_t>::max)())
                     {
                         write_number(static_cast<std::uint8_t>(0xd8));
-                        write_number(static_cast<std::uint8_t>(j.m_data.m_value.binary->subtype()));
+                        write_number(static_cast<std::uint8_t>(j.m_value.binary->subtype()));
                     }
-                    else if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits<std::uint16_t>::max)())
+                    else if (j.m_value.binary->subtype() <= (std::numeric_limits<std::uint16_t>::max)())
                     {
                         write_number(static_cast<std::uint8_t>(0xd9));
-                        write_number(static_cast<std::uint16_t>(j.m_data.m_value.binary->subtype()));
+                        write_number(static_cast<std::uint16_t>(j.m_value.binary->subtype()));
                     }
-                    else if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits<std::uint32_t>::max)())
+                    else if (j.m_value.binary->subtype() <= (std::numeric_limits<std::uint32_t>::max)())
                     {
                         write_number(static_cast<std::uint8_t>(0xda));
-                        write_number(static_cast<std::uint32_t>(j.m_data.m_value.binary->subtype()));
+                        write_number(static_cast<std::uint32_t>(j.m_value.binary->subtype()));
                     }
-                    else if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits<std::uint64_t>::max)())
+                    else if (j.m_value.binary->subtype() <= (std::numeric_limits<std::uint64_t>::max)())
                     {
                         write_number(static_cast<std::uint8_t>(0xdb));
-                        write_number(static_cast<std::uint64_t>(j.m_data.m_value.binary->subtype()));
+                        write_number(static_cast<std::uint64_t>(j.m_value.binary->subtype()));
                     }
                 }
 
                 // step 1: write control byte and the binary array size
-                const auto N = j.m_data.m_value.binary->size();
+                const auto N = j.m_value.binary->size();
                 if (N <= 0x17)
                 {
                     write_number(static_cast<std::uint8_t>(0x40 + N));
@@ -15318,7 +15270,7 @@ class binary_writer
 
                 // step 2: write each element
                 oa->write_characters(
-                    reinterpret_cast<const CharType*>(j.m_data.m_value.binary->data()),
+                    reinterpret_cast<const CharType*>(j.m_value.binary->data()),
                     N);
 
                 break;
@@ -15327,7 +15279,7 @@ class binary_writer
             case value_t::object:
             {
                 // step 1: write control byte and the object size
-                const auto N = j.m_data.m_value.object->size();
+                const auto N = j.m_value.object->size();
                 if (N <= 0x17)
                 {
                     write_number(static_cast<std::uint8_t>(0xA0 + N));
@@ -15356,7 +15308,7 @@ class binary_writer
                 // LCOV_EXCL_STOP
 
                 // step 2: write each element
-                for (const auto& el : *j.m_data.m_value.object)
+                for (const auto& el : *j.m_value.object)
                 {
                     write_cbor(el.first);
                     write_cbor(el.second);
@@ -15385,7 +15337,7 @@ class binary_writer
 
             case value_t::boolean: // true and false
             {
-                oa->write_character(j.m_data.m_value.boolean
+                oa->write_character(j.m_value.boolean
                                     ? to_char_type(0xC3)
                                     : to_char_type(0xC2));
                 break;
@@ -15393,75 +15345,75 @@ class binary_writer
 
             case value_t::number_integer:
             {
-                if (j.m_data.m_value.number_integer >= 0)
+                if (j.m_value.number_integer >= 0)
                 {
                     // MessagePack does not differentiate between positive
                     // signed integers and unsigned integers. Therefore, we used
                     // the code from the value_t::number_unsigned case here.
-                    if (j.m_data.m_value.number_unsigned < 128)
+                    if (j.m_value.number_unsigned < 128)
                     {
                         // positive fixnum
-                        write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));
+                        write_number(static_cast<std::uint8_t>(j.m_value.number_integer));
                     }
-                    else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint8_t>::max)())
+                    else if (j.m_value.number_unsigned <= (std::numeric_limits<std::uint8_t>::max)())
                     {
                         // uint 8
                         oa->write_character(to_char_type(0xCC));
-                        write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));
+                        write_number(static_cast<std::uint8_t>(j.m_value.number_integer));
                     }
-                    else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint16_t>::max)())
+                    else if (j.m_value.number_unsigned <= (std::numeric_limits<std::uint16_t>::max)())
                     {
                         // uint 16
                         oa->write_character(to_char_type(0xCD));
-                        write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_integer));
+                        write_number(static_cast<std::uint16_t>(j.m_value.number_integer));
                     }
-                    else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint32_t>::max)())
+                    else if (j.m_value.number_unsigned <= (std::numeric_limits<std::uint32_t>::max)())
                     {
                         // uint 32
                         oa->write_character(to_char_type(0xCE));
-                        write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));
+                        write_number(static_cast<std::uint32_t>(j.m_value.number_integer));
                     }
-                    else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())
+                    else if (j.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())
                     {
                         // uint 64
                         oa->write_character(to_char_type(0xCF));
-                        write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
+                        write_number(static_cast<std::uint64_t>(j.m_value.number_integer));
                     }
                 }
                 else
                 {
-                    if (j.m_data.m_value.number_integer >= -32)
+                    if (j.m_value.number_integer >= -32)
                     {
                         // negative fixnum
-                        write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
+                        write_number(static_cast<std::int8_t>(j.m_value.number_integer));
                     }
-                    else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)() &&
-                             j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
+                    else if (j.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)() &&
+                             j.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
                     {
                         // int 8
                         oa->write_character(to_char_type(0xD0));
-                        write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
+                        write_number(static_cast<std::int8_t>(j.m_value.number_integer));
                     }
-                    else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)() &&
-                             j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
+                    else if (j.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)() &&
+                             j.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
                     {
                         // int 16
                         oa->write_character(to_char_type(0xD1));
-                        write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));
+                        write_number(static_cast<std::int16_t>(j.m_value.number_integer));
                     }
-                    else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)() &&
-                             j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
+                    else if (j.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)() &&
+                             j.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
                     {
                         // int 32
                         oa->write_character(to_char_type(0xD2));
-                        write_number(static_cast<std::int32_t>(j.m_data.m_value.number_integer));
+                        write_number(static_cast<std::int32_t>(j.m_value.number_integer));
                     }
-                    else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int64_t>::min)() &&
-                             j.m_data.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)())
+                    else if (j.m_value.number_integer >= (std::numeric_limits<std::int64_t>::min)() &&
+                             j.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)())
                     {
                         // int 64
                         oa->write_character(to_char_type(0xD3));
-                        write_number(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
+                        write_number(static_cast<std::int64_t>(j.m_value.number_integer));
                     }
                 }
                 break;
@@ -15469,48 +15421,48 @@ class binary_writer
 
             case value_t::number_unsigned:
             {
-                if (j.m_data.m_value.number_unsigned < 128)
+                if (j.m_value.number_unsigned < 128)
                 {
                     // positive fixnum
-                    write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));
+                    write_number(static_cast<std::uint8_t>(j.m_value.number_integer));
                 }
-                else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint8_t>::max)())
+                else if (j.m_value.number_unsigned <= (std::numeric_limits<std::uint8_t>::max)())
                 {
                     // uint 8
                     oa->write_character(to_char_type(0xCC));
-                    write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));
+                    write_number(static_cast<std::uint8_t>(j.m_value.number_integer));
                 }
-                else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint16_t>::max)())
+                else if (j.m_value.number_unsigned <= (std::numeric_limits<std::uint16_t>::max)())
                 {
                     // uint 16
                     oa->write_character(to_char_type(0xCD));
-                    write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_integer));
+                    write_number(static_cast<std::uint16_t>(j.m_value.number_integer));
                 }
-                else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint32_t>::max)())
+                else if (j.m_value.number_unsigned <= (std::numeric_limits<std::uint32_t>::max)())
                 {
                     // uint 32
                     oa->write_character(to_char_type(0xCE));
-                    write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));
+                    write_number(static_cast<std::uint32_t>(j.m_value.number_integer));
                 }
-                else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())
+                else if (j.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())
                 {
                     // uint 64
                     oa->write_character(to_char_type(0xCF));
-                    write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
+                    write_number(static_cast<std::uint64_t>(j.m_value.number_integer));
                 }
                 break;
             }
 
             case value_t::number_float:
             {
-                write_compact_float(j.m_data.m_value.number_float, detail::input_format_t::msgpack);
+                write_compact_float(j.m_value.number_float, detail::input_format_t::msgpack);
                 break;
             }
 
             case value_t::string:
             {
                 // step 1: write control byte and the string length
-                const auto N = j.m_data.m_value.string->size();
+                const auto N = j.m_value.string->size();
                 if (N <= 31)
                 {
                     // fixstr
@@ -15537,15 +15489,15 @@ class binary_writer
 
                 // step 2: write the string
                 oa->write_characters(
-                    reinterpret_cast<const CharType*>(j.m_data.m_value.string->c_str()),
-                    j.m_data.m_value.string->size());
+                    reinterpret_cast<const CharType*>(j.m_value.string->c_str()),
+                    j.m_value.string->size());
                 break;
             }
 
             case value_t::array:
             {
                 // step 1: write control byte and the array size
-                const auto N = j.m_data.m_value.array->size();
+                const auto N = j.m_value.array->size();
                 if (N <= 15)
                 {
                     // fixarray
@@ -15565,7 +15517,7 @@ class binary_writer
                 }
 
                 // step 2: write each element
-                for (const auto& el : *j.m_data.m_value.array)
+                for (const auto& el : *j.m_value.array)
                 {
                     write_msgpack(el);
                 }
@@ -15576,10 +15528,10 @@ class binary_writer
             {
                 // step 0: determine if the binary type has a set subtype to
                 // determine whether or not to use the ext or fixext types
-                const bool use_ext = j.m_data.m_value.binary->has_subtype();
+                const bool use_ext = j.m_value.binary->has_subtype();
 
                 // step 1: write control byte and the byte string length
-                const auto N = j.m_data.m_value.binary->size();
+                const auto N = j.m_value.binary->size();
                 if (N <= (std::numeric_limits<std::uint8_t>::max)())
                 {
                     std::uint8_t output_type{};
@@ -15624,18 +15576,18 @@ class binary_writer
                 }
                 else if (N <= (std::numeric_limits<std::uint16_t>::max)())
                 {
-                    const std::uint8_t output_type = use_ext
-                                                     ? 0xC8 // ext 16
-                                                     : 0xC5; // bin 16
+                    std::uint8_t output_type = use_ext
+                                               ? 0xC8 // ext 16
+                                               : 0xC5; // bin 16
 
                     oa->write_character(to_char_type(output_type));
                     write_number(static_cast<std::uint16_t>(N));
                 }
                 else if (N <= (std::numeric_limits<std::uint32_t>::max)())
                 {
-                    const std::uint8_t output_type = use_ext
-                                                     ? 0xC9 // ext 32
-                                                     : 0xC6; // bin 32
+                    std::uint8_t output_type = use_ext
+                                               ? 0xC9 // ext 32
+                                               : 0xC6; // bin 32
 
                     oa->write_character(to_char_type(output_type));
                     write_number(static_cast<std::uint32_t>(N));
@@ -15644,12 +15596,12 @@ class binary_writer
                 // step 1.5: if this is an ext type, write the subtype
                 if (use_ext)
                 {
-                    write_number(static_cast<std::int8_t>(j.m_data.m_value.binary->subtype()));
+                    write_number(static_cast<std::int8_t>(j.m_value.binary->subtype()));
                 }
 
                 // step 2: write the byte string
                 oa->write_characters(
-                    reinterpret_cast<const CharType*>(j.m_data.m_value.binary->data()),
+                    reinterpret_cast<const CharType*>(j.m_value.binary->data()),
                     N);
 
                 break;
@@ -15658,7 +15610,7 @@ class binary_writer
             case value_t::object:
             {
                 // step 1: write control byte and the object size
-                const auto N = j.m_data.m_value.object->size();
+                const auto N = j.m_value.object->size();
                 if (N <= 15)
                 {
                     // fixmap
@@ -15678,7 +15630,7 @@ class binary_writer
                 }
 
                 // step 2: write each element
-                for (const auto& el : *j.m_data.m_value.object)
+                for (const auto& el : *j.m_value.object)
                 {
                     write_msgpack(el.first);
                     write_msgpack(el.second);
@@ -15718,7 +15670,7 @@ class binary_writer
             {
                 if (add_prefix)
                 {
-                    oa->write_character(j.m_data.m_value.boolean
+                    oa->write_character(j.m_value.boolean
                                         ? to_char_type('T')
                                         : to_char_type('F'));
                 }
@@ -15727,19 +15679,19 @@ class binary_writer
 
             case value_t::number_integer:
             {
-                write_number_with_ubjson_prefix(j.m_data.m_value.number_integer, add_prefix, use_bjdata);
+                write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix, use_bjdata);
                 break;
             }
 
             case value_t::number_unsigned:
             {
-                write_number_with_ubjson_prefix(j.m_data.m_value.number_unsigned, add_prefix, use_bjdata);
+                write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix, use_bjdata);
                 break;
             }
 
             case value_t::number_float:
             {
-                write_number_with_ubjson_prefix(j.m_data.m_value.number_float, add_prefix, use_bjdata);
+                write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix, use_bjdata);
                 break;
             }
 
@@ -15749,10 +15701,10 @@ class binary_writer
                 {
                     oa->write_character(to_char_type('S'));
                 }
-                write_number_with_ubjson_prefix(j.m_data.m_value.string->size(), true, use_bjdata);
+                write_number_with_ubjson_prefix(j.m_value.string->size(), true, use_bjdata);
                 oa->write_characters(
-                    reinterpret_cast<const CharType*>(j.m_data.m_value.string->c_str()),
-                    j.m_data.m_value.string->size());
+                    reinterpret_cast<const CharType*>(j.m_value.string->c_str()),
+                    j.m_value.string->size());
                 break;
             }
 
@@ -15764,7 +15716,7 @@ class binary_writer
                 }
 
                 bool prefix_required = true;
-                if (use_type && !j.m_data.m_value.array->empty())
+                if (use_type && !j.m_value.array->empty())
                 {
                     JSON_ASSERT(use_count);
                     const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);
@@ -15787,10 +15739,10 @@ class binary_writer
                 if (use_count)
                 {
                     oa->write_character(to_char_type('#'));
-                    write_number_with_ubjson_prefix(j.m_data.m_value.array->size(), true, use_bjdata);
+                    write_number_with_ubjson_prefix(j.m_value.array->size(), true, use_bjdata);
                 }
 
-                for (const auto& el : *j.m_data.m_value.array)
+                for (const auto& el : *j.m_value.array)
                 {
                     write_ubjson(el, use_count, use_type, prefix_required, use_bjdata);
                 }
@@ -15810,7 +15762,7 @@ class binary_writer
                     oa->write_character(to_char_type('['));
                 }
 
-                if (use_type && !j.m_data.m_value.binary->empty())
+                if (use_type && !j.m_value.binary->empty())
                 {
                     JSON_ASSERT(use_count);
                     oa->write_character(to_char_type('$'));
@@ -15820,21 +15772,21 @@ class binary_writer
                 if (use_count)
                 {
                     oa->write_character(to_char_type('#'));
-                    write_number_with_ubjson_prefix(j.m_data.m_value.binary->size(), true, use_bjdata);
+                    write_number_with_ubjson_prefix(j.m_value.binary->size(), true, use_bjdata);
                 }
 
                 if (use_type)
                 {
                     oa->write_characters(
-                        reinterpret_cast<const CharType*>(j.m_data.m_value.binary->data()),
-                        j.m_data.m_value.binary->size());
+                        reinterpret_cast<const CharType*>(j.m_value.binary->data()),
+                        j.m_value.binary->size());
                 }
                 else
                 {
-                    for (size_t i = 0; i < j.m_data.m_value.binary->size(); ++i)
+                    for (size_t i = 0; i < j.m_value.binary->size(); ++i)
                     {
                         oa->write_character(to_char_type('U'));
-                        oa->write_character(j.m_data.m_value.binary->data()[i]);
+                        oa->write_character(j.m_value.binary->data()[i]);
                     }
                 }
 
@@ -15848,9 +15800,9 @@ class binary_writer
 
             case value_t::object:
             {
-                if (use_bjdata && j.m_data.m_value.object->size() == 3 && j.m_data.m_value.object->find("_ArrayType_") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find("_ArraySize_") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find("_ArrayData_") != j.m_data.m_value.object->end())
+                if (use_bjdata && j.m_value.object->size() == 3 && j.m_value.object->find("_ArrayType_") != j.m_value.object->end() && j.m_value.object->find("_ArraySize_") != j.m_value.object->end() && j.m_value.object->find("_ArrayData_") != j.m_value.object->end())
                 {
-                    if (!write_bjdata_ndarray(*j.m_data.m_value.object, use_count, use_type))  // decode bjdata ndarray in the JData format (https://github.com/NeuroJSON/jdata)
+                    if (!write_bjdata_ndarray(*j.m_value.object, use_count, use_type))  // decode bjdata ndarray in the JData format (https://github.com/NeuroJSON/jdata)
                     {
                         break;
                     }
@@ -15862,7 +15814,7 @@ class binary_writer
                 }
 
                 bool prefix_required = true;
-                if (use_type && !j.m_data.m_value.object->empty())
+                if (use_type && !j.m_value.object->empty())
                 {
                     JSON_ASSERT(use_count);
                     const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);
@@ -15885,10 +15837,10 @@ class binary_writer
                 if (use_count)
                 {
                     oa->write_character(to_char_type('#'));
-                    write_number_with_ubjson_prefix(j.m_data.m_value.object->size(), true, use_bjdata);
+                    write_number_with_ubjson_prefix(j.m_value.object->size(), true, use_bjdata);
                 }
 
-                for (const auto& el : *j.m_data.m_value.object)
+                for (const auto& el : *j.m_value.object)
                 {
                     write_number_with_ubjson_prefix(el.first.size(), true, use_bjdata);
                     oa->write_characters(
@@ -16038,19 +15990,19 @@ class binary_writer
     void write_bson_unsigned(const string_t& name,
                              const BasicJsonType& j)
     {
-        if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))
+        if (j.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))
         {
             write_bson_entry_header(name, 0x10 /* int32 */);
-            write_number<std::int32_t>(static_cast<std::int32_t>(j.m_data.m_value.number_unsigned), true);
+            write_number<std::int32_t>(static_cast<std::int32_t>(j.m_value.number_unsigned), true);
         }
-        else if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
+        else if (j.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
         {
             write_bson_entry_header(name, 0x12 /* int64 */);
-            write_number<std::int64_t>(static_cast<std::int64_t>(j.m_data.m_value.number_unsigned), true);
+            write_number<std::int64_t>(static_cast<std::int64_t>(j.m_value.number_unsigned), true);
         }
         else
         {
-            JSON_THROW(out_of_range::create(407, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BSON as it does not fit int64"), &j));
+            JSON_THROW(out_of_range::create(407, concat("integer number ", std::to_string(j.m_value.number_unsigned), " cannot be represented by BSON as it does not fit int64"), &j));
         }
     }
 
@@ -16131,13 +16083,13 @@ class binary_writer
         switch (j.type())
         {
             case value_t::object:
-                return header_size + calc_bson_object_size(*j.m_data.m_value.object);
+                return header_size + calc_bson_object_size(*j.m_value.object);
 
             case value_t::array:
-                return header_size + calc_bson_array_size(*j.m_data.m_value.array);
+                return header_size + calc_bson_array_size(*j.m_value.array);
 
             case value_t::binary:
-                return header_size + calc_bson_binary_size(*j.m_data.m_value.binary);
+                return header_size + calc_bson_binary_size(*j.m_value.binary);
 
             case value_t::boolean:
                 return header_size + 1ul;
@@ -16146,13 +16098,13 @@ class binary_writer
                 return header_size + 8ul;
 
             case value_t::number_integer:
-                return header_size + calc_bson_integer_size(j.m_data.m_value.number_integer);
+                return header_size + calc_bson_integer_size(j.m_value.number_integer);
 
             case value_t::number_unsigned:
-                return header_size + calc_bson_unsigned_size(j.m_data.m_value.number_unsigned);
+                return header_size + calc_bson_unsigned_size(j.m_value.number_unsigned);
 
             case value_t::string:
-                return header_size + calc_bson_string_size(*j.m_data.m_value.string);
+                return header_size + calc_bson_string_size(*j.m_value.string);
 
             case value_t::null:
                 return header_size + 0ul;
@@ -16178,28 +16130,28 @@ class binary_writer
         switch (j.type())
         {
             case value_t::object:
-                return write_bson_object_entry(name, *j.m_data.m_value.object);
+                return write_bson_object_entry(name, *j.m_value.object);
 
             case value_t::array:
-                return write_bson_array(name, *j.m_data.m_value.array);
+                return write_bson_array(name, *j.m_value.array);
 
             case value_t::binary:
-                return write_bson_binary(name, *j.m_data.m_value.binary);
+                return write_bson_binary(name, *j.m_value.binary);
 
             case value_t::boolean:
-                return write_bson_boolean(name, j.m_data.m_value.boolean);
+                return write_bson_boolean(name, j.m_value.boolean);
 
             case value_t::number_float:
-                return write_bson_double(name, j.m_data.m_value.number_float);
+                return write_bson_double(name, j.m_value.number_float);
 
             case value_t::number_integer:
-                return write_bson_integer(name, j.m_data.m_value.number_integer);
+                return write_bson_integer(name, j.m_value.number_integer);
 
             case value_t::number_unsigned:
                 return write_bson_unsigned(name, j);
 
             case value_t::string:
-                return write_bson_string(name, *j.m_data.m_value.string);
+                return write_bson_string(name, *j.m_value.string);
 
             case value_t::null:
                 return write_bson_null(name);
@@ -16221,8 +16173,8 @@ class binary_writer
     */
     static std::size_t calc_bson_object_size(const typename BasicJsonType::object_t& value)
     {
-        const std::size_t document_size = std::accumulate(value.begin(), value.end(), static_cast<std::size_t>(0),
-                                          [](size_t result, const typename BasicJsonType::object_t::value_type & el)
+        std::size_t document_size = std::accumulate(value.begin(), value.end(), static_cast<std::size_t>(0),
+                                    [](size_t result, const typename BasicJsonType::object_t::value_type & el)
         {
             return result += calc_bson_element_size(el.first, el.second);
         });
@@ -16472,35 +16424,35 @@ class binary_writer
                 return 'Z';
 
             case value_t::boolean:
-                return j.m_data.m_value.boolean ? 'T' : 'F';
+                return j.m_value.boolean ? 'T' : 'F';
 
             case value_t::number_integer:
             {
-                if ((std::numeric_limits<std::int8_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
+                if ((std::numeric_limits<std::int8_t>::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
                 {
                     return 'i';
                 }
-                if ((std::numeric_limits<std::uint8_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint8_t>::max)())
+                if ((std::numeric_limits<std::uint8_t>::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits<std::uint8_t>::max)())
                 {
                     return 'U';
                 }
-                if ((std::numeric_limits<std::int16_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
+                if ((std::numeric_limits<std::int16_t>::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
                 {
                     return 'I';
                 }
-                if (use_bjdata && ((std::numeric_limits<std::uint16_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint16_t>::max)()))
+                if (use_bjdata && ((std::numeric_limits<std::uint16_t>::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits<std::uint16_t>::max)()))
                 {
                     return 'u';
                 }
-                if ((std::numeric_limits<std::int32_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
+                if ((std::numeric_limits<std::int32_t>::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
                 {
                     return 'l';
                 }
-                if (use_bjdata && ((std::numeric_limits<std::uint32_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint32_t>::max)()))
+                if (use_bjdata && ((std::numeric_limits<std::uint32_t>::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits<std::uint32_t>::max)()))
                 {
                     return 'm';
                 }
-                if ((std::numeric_limits<std::int64_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)())
+                if ((std::numeric_limits<std::int64_t>::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)())
                 {
                     return 'L';
                 }
@@ -16510,35 +16462,35 @@ class binary_writer
 
             case value_t::number_unsigned:
             {
-                if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int8_t>::max)()))
+                if (j.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int8_t>::max)()))
                 {
                     return 'i';
                 }
-                if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint8_t>::max)()))
+                if (j.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint8_t>::max)()))
                 {
                     return 'U';
                 }
-                if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int16_t>::max)()))
+                if (j.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int16_t>::max)()))
                 {
                     return 'I';
                 }
-                if (use_bjdata && j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint16_t>::max)()))
+                if (use_bjdata && j.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint16_t>::max)()))
                 {
                     return 'u';
                 }
-                if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))
+                if (j.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))
                 {
                     return 'l';
                 }
-                if (use_bjdata && j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint32_t>::max)()))
+                if (use_bjdata && j.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint32_t>::max)()))
                 {
                     return 'm';
                 }
-                if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
+                if (j.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
                 {
                     return 'L';
                 }
-                if (use_bjdata && j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())
+                if (use_bjdata && j.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())
                 {
                     return 'M';
                 }
@@ -16547,7 +16499,7 @@ class binary_writer
             }
 
             case value_t::number_float:
-                return get_ubjson_float_prefix(j.m_data.m_value.number_float);
+                return get_ubjson_float_prefix(j.m_value.number_float);
 
             case value_t::string:
                 return 'S';
@@ -16596,7 +16548,7 @@ class binary_writer
         std::size_t len = (value.at(key).empty() ? 0 : 1);
         for (const auto& el : value.at(key))
         {
-            len *= static_cast<std::size_t>(el.m_data.m_value.number_unsigned);
+            len *= static_cast<std::size_t>(el.m_value.number_unsigned);
         }
 
         key = "_ArrayData_";
@@ -16618,70 +16570,70 @@ class binary_writer
         {
             for (const auto& el : value.at(key))
             {
-                write_number(static_cast<std::uint8_t>(el.m_data.m_value.number_unsigned), true);
+                write_number(static_cast<std::uint8_t>(el.m_value.number_unsigned), true);
             }
         }
         else if (dtype == 'i')
         {
             for (const auto& el : value.at(key))
             {
-                write_number(static_cast<std::int8_t>(el.m_data.m_value.number_integer), true);
+                write_number(static_cast<std::int8_t>(el.m_value.number_integer), true);
             }
         }
         else if (dtype == 'u')
         {
             for (const auto& el : value.at(key))
             {
-                write_number(static_cast<std::uint16_t>(el.m_data.m_value.number_unsigned), true);
+                write_number(static_cast<std::uint16_t>(el.m_value.number_unsigned), true);
             }
         }
         else if (dtype == 'I')
         {
             for (const auto& el : value.at(key))
             {
-                write_number(static_cast<std::int16_t>(el.m_data.m_value.number_integer), true);
+                write_number(static_cast<std::int16_t>(el.m_value.number_integer), true);
             }
         }
         else if (dtype == 'm')
         {
             for (const auto& el : value.at(key))
             {
-                write_number(static_cast<std::uint32_t>(el.m_data.m_value.number_unsigned), true);
+                write_number(static_cast<std::uint32_t>(el.m_value.number_unsigned), true);
             }
         }
         else if (dtype == 'l')
         {
             for (const auto& el : value.at(key))
             {
-                write_number(static_cast<std::int32_t>(el.m_data.m_value.number_integer), true);
+                write_number(static_cast<std::int32_t>(el.m_value.number_integer), true);
             }
         }
         else if (dtype == 'M')
         {
             for (const auto& el : value.at(key))
             {
-                write_number(static_cast<std::uint64_t>(el.m_data.m_value.number_unsigned), true);
+                write_number(static_cast<std::uint64_t>(el.m_value.number_unsigned), true);
             }
         }
         else if (dtype == 'L')
         {
             for (const auto& el : value.at(key))
             {
-                write_number(static_cast<std::int64_t>(el.m_data.m_value.number_integer), true);
+                write_number(static_cast<std::int64_t>(el.m_value.number_integer), true);
             }
         }
         else if (dtype == 'd')
         {
             for (const auto& el : value.at(key))
             {
-                write_number(static_cast<float>(el.m_data.m_value.number_float), true);
+                write_number(static_cast<float>(el.m_value.number_float), true);
             }
         }
         else if (dtype == 'D')
         {
             for (const auto& el : value.at(key))
             {
-                write_number(static_cast<double>(el.m_data.m_value.number_float), true);
+                write_number(static_cast<double>(el.m_value.number_float), true);
             }
         }
         return false;
@@ -17740,7 +17692,7 @@ void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value)
     // NB: If the neighbors are computed for single-precision numbers, there is a single float
     //     (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision
     //     value is off by 1 ulp.
-#if 0 // NOLINT(readability-avoid-unconditional-preprocessor-if)
+#if 0
     const boundaries w = compute_boundaries(static_cast<double>(value));
 #else
     const boundaries w = compute_boundaries(value);
@@ -18042,11 +17994,11 @@ class serializer
               const unsigned int indent_step,
               const unsigned int current_indent = 0)
     {
-        switch (val.m_data.m_type)
+        switch (val.m_type)
         {
             case value_t::object:
             {
-                if (val.m_data.m_value.object->empty())
+                if (val.m_value.object->empty())
                 {
                     o->write_characters("{}", 2);
                     return;
@@ -18064,8 +18016,8 @@ class serializer
                     }
 
                     // first n-1 elements
-                    auto i = val.m_data.m_value.object->cbegin();
-                    for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
+                    auto i = val.m_value.object->cbegin();
+                    for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i)
                     {
                         o->write_characters(indent_string.c_str(), new_indent);
                         o->write_character('\"');
@@ -18076,8 +18028,8 @@ class serializer
                     }
 
                     // last element
-                    JSON_ASSERT(i != val.m_data.m_value.object->cend());
-                    JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
+                    JSON_ASSERT(i != val.m_value.object->cend());
+                    JSON_ASSERT(std::next(i) == val.m_value.object->cend());
                     o->write_characters(indent_string.c_str(), new_indent);
                     o->write_character('\"');
                     dump_escaped(i->first, ensure_ascii);
@@ -18093,8 +18045,8 @@ class serializer
                     o->write_character('{');
 
                     // first n-1 elements
-                    auto i = val.m_data.m_value.object->cbegin();
-                    for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
+                    auto i = val.m_value.object->cbegin();
+                    for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i)
                     {
                         o->write_character('\"');
                         dump_escaped(i->first, ensure_ascii);
@@ -18104,8 +18056,8 @@ class serializer
                     }
 
                     // last element
-                    JSON_ASSERT(i != val.m_data.m_value.object->cend());
-                    JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
+                    JSON_ASSERT(i != val.m_value.object->cend());
+                    JSON_ASSERT(std::next(i) == val.m_value.object->cend());
                     o->write_character('\"');
                     dump_escaped(i->first, ensure_ascii);
                     o->write_characters("\":", 2);
@@ -18119,7 +18071,7 @@ class serializer
 
             case value_t::array:
             {
-                if (val.m_data.m_value.array->empty())
+                if (val.m_value.array->empty())
                 {
                     o->write_characters("[]", 2);
                     return;
@@ -18137,8 +18089,8 @@ class serializer
                     }
 
                     // first n-1 elements
-                    for (auto i = val.m_data.m_value.array->cbegin();
-                            i != val.m_data.m_value.array->cend() - 1; ++i)
+                    for (auto i = val.m_value.array->cbegin();
+                            i != val.m_value.array->cend() - 1; ++i)
                     {
                         o->write_characters(indent_string.c_str(), new_indent);
                         dump(*i, true, ensure_ascii, indent_step, new_indent);
@@ -18146,9 +18098,9 @@ class serializer
                     }
 
                     // last element
-                    JSON_ASSERT(!val.m_data.m_value.array->empty());
+                    JSON_ASSERT(!val.m_value.array->empty());
                     o->write_characters(indent_string.c_str(), new_indent);
-                    dump(val.m_data.m_value.array->back(), true, ensure_ascii, indent_step, new_indent);
+                    dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent);
 
                     o->write_character('\n');
                     o->write_characters(indent_string.c_str(), current_indent);
@@ -18159,16 +18111,16 @@ class serializer
                     o->write_character('[');
 
                     // first n-1 elements
-                    for (auto i = val.m_data.m_value.array->cbegin();
-                            i != val.m_data.m_value.array->cend() - 1; ++i)
+                    for (auto i = val.m_value.array->cbegin();
+                            i != val.m_value.array->cend() - 1; ++i)
                     {
                         dump(*i, false, ensure_ascii, indent_step, current_indent);
                         o->write_character(',');
                     }
 
                     // last element
-                    JSON_ASSERT(!val.m_data.m_value.array->empty());
-                    dump(val.m_data.m_value.array->back(), false, ensure_ascii, indent_step, current_indent);
+                    JSON_ASSERT(!val.m_value.array->empty());
+                    dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent);
 
                     o->write_character(']');
                 }
@@ -18179,7 +18131,7 @@ class serializer
             case value_t::string:
             {
                 o->write_character('\"');
-                dump_escaped(*val.m_data.m_value.string, ensure_ascii);
+                dump_escaped(*val.m_value.string, ensure_ascii);
                 o->write_character('\"');
                 return;
             }
@@ -18201,24 +18153,24 @@ class serializer
 
                     o->write_characters("\"bytes\": [", 10);
 
-                    if (!val.m_data.m_value.binary->empty())
+                    if (!val.m_value.binary->empty())
                     {
-                        for (auto i = val.m_data.m_value.binary->cbegin();
-                                i != val.m_data.m_value.binary->cend() - 1; ++i)
+                        for (auto i = val.m_value.binary->cbegin();
+                                i != val.m_value.binary->cend() - 1; ++i)
                         {
                             dump_integer(*i);
                             o->write_characters(", ", 2);
                         }
-                        dump_integer(val.m_data.m_value.binary->back());
+                        dump_integer(val.m_value.binary->back());
                     }
 
                     o->write_characters("],\n", 3);
                     o->write_characters(indent_string.c_str(), new_indent);
 
                     o->write_characters("\"subtype\": ", 11);
-                    if (val.m_data.m_value.binary->has_subtype())
+                    if (val.m_value.binary->has_subtype())
                     {
-                        dump_integer(val.m_data.m_value.binary->subtype());
+                        dump_integer(val.m_value.binary->subtype());
                     }
                     else
                     {
@@ -18232,21 +18184,21 @@ class serializer
                 {
                     o->write_characters("{\"bytes\":[", 10);
 
-                    if (!val.m_data.m_value.binary->empty())
+                    if (!val.m_value.binary->empty())
                     {
-                        for (auto i = val.m_data.m_value.binary->cbegin();
-                                i != val.m_data.m_value.binary->cend() - 1; ++i)
+                        for (auto i = val.m_value.binary->cbegin();
+                                i != val.m_value.binary->cend() - 1; ++i)
                         {
                             dump_integer(*i);
                             o->write_character(',');
                         }
-                        dump_integer(val.m_data.m_value.binary->back());
+                        dump_integer(val.m_value.binary->back());
                     }
 
                     o->write_characters("],\"subtype\":", 12);
-                    if (val.m_data.m_value.binary->has_subtype())
+                    if (val.m_value.binary->has_subtype())
                     {
-                        dump_integer(val.m_data.m_value.binary->subtype());
+                        dump_integer(val.m_value.binary->subtype());
                         o->write_character('}');
                     }
                     else
@@ -18259,7 +18211,7 @@ class serializer
 
             case value_t::boolean:
             {
-                if (val.m_data.m_value.boolean)
+                if (val.m_value.boolean)
                 {
                     o->write_characters("true", 4);
                 }
@@ -18272,19 +18224,19 @@ class serializer
 
             case value_t::number_integer:
             {
-                dump_integer(val.m_data.m_value.number_integer);
+                dump_integer(val.m_value.number_integer);
                 return;
             }
 
             case value_t::number_unsigned:
             {
-                dump_integer(val.m_data.m_value.number_unsigned);
+                dump_integer(val.m_value.number_unsigned);
                 return;
             }
 
             case value_t::number_float:
             {
-                dump_float(val.m_data.m_value.number_float);
+                dump_float(val.m_value.number_float);
                 return;
             }
 
@@ -18858,8 +18810,8 @@ class serializer
                 ? (byte & 0x3fu) | (codep << 6u)
                 : (0xFFu >> type) & (byte);
 
-        const std::size_t index = 256u + static_cast<size_t>(state) * 16u + static_cast<size_t>(type);
-        JSON_ASSERT(index < utf8d.size());
+        std::size_t index = 256u + static_cast<size_t>(state) * 16u + static_cast<size_t>(type);
+        JSON_ASSERT(index < 400);
         state = utf8d[index];
         return state;
     }
@@ -19046,7 +18998,7 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
 
     template<class KeyType, detail::enable_if_t<
                  detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
-    T & at(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)
+    T & at(KeyType && key)
     {
         for (auto it = this->begin(); it != this->end(); ++it)
         {
@@ -19074,7 +19026,7 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
 
     template<class KeyType, detail::enable_if_t<
                  detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
-    const T & at(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward)
+    const T & at(KeyType && key) const
     {
         for (auto it = this->begin(); it != this->end(); ++it)
         {
@@ -19108,7 +19060,7 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
 
     template<class KeyType, detail::enable_if_t<
                  detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
-    size_type erase(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)
+    size_type erase(KeyType && key)
     {
         for (auto it = this->begin(); it != this->end(); ++it)
         {
@@ -19199,7 +19151,7 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
 
     template<class KeyType, detail::enable_if_t<
                  detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
-    size_type count(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward)
+    size_type count(KeyType && key) const
     {
         for (auto it = this->begin(); it != this->end(); ++it)
         {
@@ -19225,7 +19177,7 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
 
     template<class KeyType, detail::enable_if_t<
                  detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
-    iterator find(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)
+    iterator find(KeyType && key)
     {
         for (auto it = this->begin(); it != this->end(); ++it)
         {
@@ -19288,9 +19240,7 @@ NLOHMANN_JSON_NAMESPACE_END
 
 
 #if defined(JSON_HAS_CPP_17)
-    #if JSON_HAS_STATIC_RTTI
-        #include <any>
-    #endif
+    #include <any>
     #include <string_view>
 #endif
 
@@ -19321,7 +19271,6 @@ The invariants are checked by member function assert_invariant().
 */
 NLOHMANN_BASIC_JSON_TPL_DECLARATION
 class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions)
-    : public ::nlohmann::detail::json_base_class<CustomBaseClass>
 {
   private:
     template<detail::value_t> friend struct detail::external_constructor;
@@ -19348,7 +19297,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
 
     /// workaround type for MSVC
     using basic_json_t = NLOHMANN_BASIC_JSON_TPL;
-    using json_base_class_t = ::nlohmann::detail::json_base_class<CustomBaseClass>;
 
   JSON_PRIVATE_UNLESS_TESTED:
     // convenience aliases for types residing in namespace detail;
@@ -19420,6 +19368,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
 
     /// @}
 
+
     /////////////////////
     // container types //
     /////////////////////
@@ -19461,6 +19410,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
 
     /// @}
 
+
     /// @brief returns the allocator associated with the container
     /// @sa https://json.nlohmann.me/api/basic_json/get_allocator/
     static allocator_type get_allocator()
@@ -19523,6 +19473,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}};
 #endif
 
+
 #if defined(_MSVC_LANG)
         result["compiler"]["c++"] = std::to_string(_MSVC_LANG);
 #elif defined(__cplusplus)
@@ -19533,6 +19484,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         return result;
     }
 
+
     ///////////////////////////
     // JSON value data types //
     ///////////////////////////
@@ -19779,16 +19731,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
 
         void destroy(value_t t)
         {
-            if (
-                (t == value_t::object && object == nullptr) ||
-                (t == value_t::array && array == nullptr) ||
-                (t == value_t::string && string == nullptr) ||
-                (t == value_t::binary && binary == nullptr)
-            )
-            {
-                //not initialized (e.g. due to exception in the ctor)
-                return;
-            }
             if (t == value_t::array || t == value_t::object)
             {
                 // flatten the current json_value to a heap-allocated stack
@@ -19819,18 +19761,18 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
                     // its children to the stack to be processed later
                     if (current_item.is_array())
                     {
-                        std::move(current_item.m_data.m_value.array->begin(), current_item.m_data.m_value.array->end(), std::back_inserter(stack));
+                        std::move(current_item.m_value.array->begin(), current_item.m_value.array->end(), std::back_inserter(stack));
 
-                        current_item.m_data.m_value.array->clear();
+                        current_item.m_value.array->clear();
                     }
                     else if (current_item.is_object())
                     {
-                        for (auto&& it : *current_item.m_data.m_value.object)
+                        for (auto&& it : *current_item.m_value.object)
                         {
                             stack.push_back(std::move(it.second));
                         }
 
-                        current_item.m_data.m_value.object->clear();
+                        current_item.m_value.object->clear();
                     }
 
                     // it's now safe that current_item get destructed
@@ -19907,10 +19849,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     */
     void assert_invariant(bool check_parents = true) const noexcept
     {
-        JSON_ASSERT(m_data.m_type != value_t::object || m_data.m_value.object != nullptr);
-        JSON_ASSERT(m_data.m_type != value_t::array || m_data.m_value.array != nullptr);
-        JSON_ASSERT(m_data.m_type != value_t::string || m_data.m_value.string != nullptr);
-        JSON_ASSERT(m_data.m_type != value_t::binary || m_data.m_value.binary != nullptr);
+        JSON_ASSERT(m_type != value_t::object || m_value.object != nullptr);
+        JSON_ASSERT(m_type != value_t::array || m_value.array != nullptr);
+        JSON_ASSERT(m_type != value_t::string || m_value.string != nullptr);
+        JSON_ASSERT(m_type != value_t::binary || m_value.binary != nullptr);
 
 #if JSON_DIAGNOSTICS
         JSON_TRY
@@ -19929,11 +19871,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     void set_parents()
     {
 #if JSON_DIAGNOSTICS
-        switch (m_data.m_type)
+        switch (m_type)
         {
             case value_t::array:
             {
-                for (auto& element : *m_data.m_value.array)
+                for (auto& element : *m_value.array)
                 {
                     element.m_parent = this;
                 }
@@ -19942,7 +19884,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
 
             case value_t::object:
             {
-                for (auto& element : *m_data.m_value.object)
+                for (auto& element : *m_value.object)
                 {
                     element.second.m_parent = this;
                 }
@@ -19983,7 +19925,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         {
             // see https://github.com/nlohmann/json/issues/2838
             JSON_ASSERT(type() == value_t::array);
-            if (JSON_HEDLEY_UNLIKELY(m_data.m_value.array->capacity() != old_capacity))
+            if (JSON_HEDLEY_UNLIKELY(m_value.array->capacity() != old_capacity))
             {
                 // capacity has changed: update all parents
                 set_parents();
@@ -20039,7 +19981,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     /// @brief create an empty value with a given type
     /// @sa https://json.nlohmann.me/api/basic_json/basic_json/
     basic_json(const value_t v)
-        : m_data(v)
+        : m_type(v), m_value(v)
     {
         assert_invariant();
     }
@@ -20113,12 +20055,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
                 *this = nullptr;
                 break;
             case value_t::discarded:
-                m_data.m_type = value_t::discarded;
+                m_type = value_t::discarded;
                 break;
             default:            // LCOV_EXCL_LINE
                 JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
         }
-        JSON_ASSERT(m_data.m_type == val.type());
+        JSON_ASSERT(m_type == val.type());
         set_parents();
         assert_invariant();
     }
@@ -20134,10 +20076,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         bool is_an_object = std::all_of(init.begin(), init.end(),
                                         [](const detail::json_ref<basic_json>& element_ref)
         {
-            // The cast is to ensure op[size_type] is called, bearing in mind size_type may not be int;
-            // (many string types can be constructed from 0 via its null-pointer guise, so we get a
-            // broken call to op[key_type], the wrong semantics and a 4804 warning on Windows)
-            return element_ref->is_array() && element_ref->size() == 2 && (*element_ref)[static_cast<size_type>(0)].is_string();
+            return element_ref->is_array() && element_ref->size() == 2 && (*element_ref)[0].is_string();
         });
 
         // adjust type if type deduction is not wanted
@@ -20159,22 +20098,22 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         if (is_an_object)
         {
             // the initializer list is a list of pairs -> create object
-            m_data.m_type = value_t::object;
-            m_data.m_value = value_t::object;
+            m_type = value_t::object;
+            m_value = value_t::object;
 
             for (auto& element_ref : init)
             {
                 auto element = element_ref.moved_or_copied();
-                m_data.m_value.object->emplace(
-                    std::move(*((*element.m_data.m_value.array)[0].m_data.m_value.string)),
-                    std::move((*element.m_data.m_value.array)[1]));
+                m_value.object->emplace(
+                    std::move(*((*element.m_value.array)[0].m_value.string)),
+                    std::move((*element.m_value.array)[1]));
             }
         }
         else
         {
             // the initializer list describes an array -> create array
-            m_data.m_type = value_t::array;
-            m_data.m_value.array = create<array_t>(init.begin(), init.end());
+            m_type = value_t::array;
+            m_value.array = create<array_t>(init.begin(), init.end());
         }
 
         set_parents();
@@ -20187,8 +20126,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     static basic_json binary(const typename binary_t::container_type& init)
     {
         auto res = basic_json();
-        res.m_data.m_type = value_t::binary;
-        res.m_data.m_value = init;
+        res.m_type = value_t::binary;
+        res.m_value = init;
         return res;
     }
 
@@ -20198,8 +20137,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     static basic_json binary(const typename binary_t::container_type& init, typename binary_t::subtype_type subtype)
     {
         auto res = basic_json();
-        res.m_data.m_type = value_t::binary;
-        res.m_data.m_value = binary_t(init, subtype);
+        res.m_type = value_t::binary;
+        res.m_value = binary_t(init, subtype);
         return res;
     }
 
@@ -20209,8 +20148,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     static basic_json binary(typename binary_t::container_type&& init)
     {
         auto res = basic_json();
-        res.m_data.m_type = value_t::binary;
-        res.m_data.m_value = std::move(init);
+        res.m_type = value_t::binary;
+        res.m_value = std::move(init);
         return res;
     }
 
@@ -20220,8 +20159,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     static basic_json binary(typename binary_t::container_type&& init, typename binary_t::subtype_type subtype)
     {
         auto res = basic_json();
-        res.m_data.m_type = value_t::binary;
-        res.m_data.m_value = binary_t(std::move(init), subtype);
+        res.m_type = value_t::binary;
+        res.m_value = binary_t(std::move(init), subtype);
         return res;
     }
 
@@ -20243,9 +20182,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
 
     /// @brief construct an array with count copies of given value
     /// @sa https://json.nlohmann.me/api/basic_json/basic_json/
-    basic_json(size_type cnt, const basic_json& val):
-        m_data{cnt, val}
+    basic_json(size_type cnt, const basic_json& val)
+        : m_type(value_t::array)
     {
+        m_value.array = create<array_t>(cnt, val);
         set_parents();
         assert_invariant();
     }
@@ -20267,10 +20207,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         }
 
         // copy type from first iterator
-        m_data.m_type = first.m_object->m_data.m_type;
+        m_type = first.m_object->m_type;
 
         // check if iterator range is complete for primitive values
-        switch (m_data.m_type)
+        switch (m_type)
         {
             case value_t::boolean:
             case value_t::number_float:
@@ -20295,55 +20235,55 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
                 break;
         }
 
-        switch (m_data.m_type)
+        switch (m_type)
         {
             case value_t::number_integer:
             {
-                m_data.m_value.number_integer = first.m_object->m_data.m_value.number_integer;
+                m_value.number_integer = first.m_object->m_value.number_integer;
                 break;
             }
 
             case value_t::number_unsigned:
             {
-                m_data.m_value.number_unsigned = first.m_object->m_data.m_value.number_unsigned;
+                m_value.number_unsigned = first.m_object->m_value.number_unsigned;
                 break;
             }
 
             case value_t::number_float:
             {
-                m_data.m_value.number_float = first.m_object->m_data.m_value.number_float;
+                m_value.number_float = first.m_object->m_value.number_float;
                 break;
             }
 
             case value_t::boolean:
             {
-                m_data.m_value.boolean = first.m_object->m_data.m_value.boolean;
+                m_value.boolean = first.m_object->m_value.boolean;
                 break;
             }
 
             case value_t::string:
             {
-                m_data.m_value = *first.m_object->m_data.m_value.string;
+                m_value = *first.m_object->m_value.string;
                 break;
             }
 
             case value_t::object:
             {
-                m_data.m_value.object = create<object_t>(first.m_it.object_iterator,
-                                        last.m_it.object_iterator);
+                m_value.object = create<object_t>(first.m_it.object_iterator,
+                                                  last.m_it.object_iterator);
                 break;
             }
 
             case value_t::array:
             {
-                m_data.m_value.array = create<array_t>(first.m_it.array_iterator,
-                                                       last.m_it.array_iterator);
+                m_value.array = create<array_t>(first.m_it.array_iterator,
+                                                last.m_it.array_iterator);
                 break;
             }
 
             case value_t::binary:
             {
-                m_data.m_value = *first.m_object->m_data.m_value.binary;
+                m_value = *first.m_object->m_value.binary;
                 break;
             }
 
@@ -20357,6 +20297,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         assert_invariant();
     }
 
+
     ///////////////////////////////////////
     // other constructors and destructor //
     ///////////////////////////////////////
@@ -20369,59 +20310,58 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     /// @brief copy constructor
     /// @sa https://json.nlohmann.me/api/basic_json/basic_json/
     basic_json(const basic_json& other)
-        : json_base_class_t(other)
+        : m_type(other.m_type)
     {
-        m_data.m_type = other.m_data.m_type;
         // check of passed value is valid
         other.assert_invariant();
 
-        switch (m_data.m_type)
+        switch (m_type)
         {
             case value_t::object:
             {
-                m_data.m_value = *other.m_data.m_value.object;
+                m_value = *other.m_value.object;
                 break;
             }
 
             case value_t::array:
             {
-                m_data.m_value = *other.m_data.m_value.array;
+                m_value = *other.m_value.array;
                 break;
             }
 
             case value_t::string:
             {
-                m_data.m_value = *other.m_data.m_value.string;
+                m_value = *other.m_value.string;
                 break;
             }
 
             case value_t::boolean:
             {
-                m_data.m_value = other.m_data.m_value.boolean;
+                m_value = other.m_value.boolean;
                 break;
             }
 
             case value_t::number_integer:
             {
-                m_data.m_value = other.m_data.m_value.number_integer;
+                m_value = other.m_value.number_integer;
                 break;
             }
 
             case value_t::number_unsigned:
             {
-                m_data.m_value = other.m_data.m_value.number_unsigned;
+                m_value = other.m_value.number_unsigned;
                 break;
             }
 
             case value_t::number_float:
             {
-                m_data.m_value = other.m_data.m_value.number_float;
+                m_value = other.m_value.number_float;
                 break;
             }
 
             case value_t::binary:
             {
-                m_data.m_value = *other.m_data.m_value.binary;
+                m_value = *other.m_value.binary;
                 break;
             }
 
@@ -20438,15 +20378,15 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     /// @brief move constructor
     /// @sa https://json.nlohmann.me/api/basic_json/basic_json/
     basic_json(basic_json&& other) noexcept
-        : json_base_class_t(std::forward<json_base_class_t>(other)),
-          m_data(std::move(other.m_data))
+        : m_type(std::move(other.m_type)),
+          m_value(std::move(other.m_value))
     {
         // check that passed value is valid
         other.assert_invariant(false);
 
         // invalidate payload
-        other.m_data.m_type = value_t::null;
-        other.m_data.m_value = {};
+        other.m_type = value_t::null;
+        other.m_value = {};
 
         set_parents();
         assert_invariant();
@@ -20458,17 +20398,15 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         std::is_nothrow_move_constructible<value_t>::value&&
         std::is_nothrow_move_assignable<value_t>::value&&
         std::is_nothrow_move_constructible<json_value>::value&&
-        std::is_nothrow_move_assignable<json_value>::value&&
-        std::is_nothrow_move_assignable<json_base_class_t>::value
+        std::is_nothrow_move_assignable<json_value>::value
     )
     {
         // check that passed value is valid
         other.assert_invariant();
 
         using std::swap;
-        swap(m_data.m_type, other.m_data.m_type);
-        swap(m_data.m_value, other.m_data.m_value);
-        json_base_class_t::operator=(std::move(other));
+        swap(m_type, other.m_type);
+        swap(m_value, other.m_value);
 
         set_parents();
         assert_invariant();
@@ -20480,6 +20418,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     ~basic_json() noexcept
     {
         assert_invariant(false);
+        m_value.destroy(m_type);
     }
 
     /// @}
@@ -20519,7 +20458,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     /// @sa https://json.nlohmann.me/api/basic_json/type/
     constexpr value_t type() const noexcept
     {
-        return m_data.m_type;
+        return m_type;
     }
 
     /// @brief return whether type is primitive
@@ -20540,14 +20479,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     /// @sa https://json.nlohmann.me/api/basic_json/is_null/
     constexpr bool is_null() const noexcept
     {
-        return m_data.m_type == value_t::null;
+        return m_type == value_t::null;
     }
 
     /// @brief return whether value is a boolean
     /// @sa https://json.nlohmann.me/api/basic_json/is_boolean/
     constexpr bool is_boolean() const noexcept
     {
-        return m_data.m_type == value_t::boolean;
+        return m_type == value_t::boolean;
     }
 
     /// @brief return whether value is a number
@@ -20561,63 +20500,63 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     /// @sa https://json.nlohmann.me/api/basic_json/is_number_integer/
     constexpr bool is_number_integer() const noexcept
     {
-        return m_data.m_type == value_t::number_integer || m_data.m_type == value_t::number_unsigned;
+        return m_type == value_t::number_integer || m_type == value_t::number_unsigned;
     }
 
     /// @brief return whether value is an unsigned integer number
     /// @sa https://json.nlohmann.me/api/basic_json/is_number_unsigned/
     constexpr bool is_number_unsigned() const noexcept
     {
-        return m_data.m_type == value_t::number_unsigned;
+        return m_type == value_t::number_unsigned;
     }
 
     /// @brief return whether value is a floating-point number
     /// @sa https://json.nlohmann.me/api/basic_json/is_number_float/
     constexpr bool is_number_float() const noexcept
     {
-        return m_data.m_type == value_t::number_float;
+        return m_type == value_t::number_float;
     }
 
     /// @brief return whether value is an object
     /// @sa https://json.nlohmann.me/api/basic_json/is_object/
     constexpr bool is_object() const noexcept
     {
-        return m_data.m_type == value_t::object;
+        return m_type == value_t::object;
     }
 
     /// @brief return whether value is an array
     /// @sa https://json.nlohmann.me/api/basic_json/is_array/
     constexpr bool is_array() const noexcept
     {
-        return m_data.m_type == value_t::array;
+        return m_type == value_t::array;
     }
 
     /// @brief return whether value is a string
     /// @sa https://json.nlohmann.me/api/basic_json/is_string/
     constexpr bool is_string() const noexcept
     {
-        return m_data.m_type == value_t::string;
+        return m_type == value_t::string;
     }
 
     /// @brief return whether value is a binary array
     /// @sa https://json.nlohmann.me/api/basic_json/is_binary/
     constexpr bool is_binary() const noexcept
     {
-        return m_data.m_type == value_t::binary;
+        return m_type == value_t::binary;
     }
 
     /// @brief return whether value is discarded
     /// @sa https://json.nlohmann.me/api/basic_json/is_discarded/
     constexpr bool is_discarded() const noexcept
     {
-        return m_data.m_type == value_t::discarded;
+        return m_type == value_t::discarded;
     }
 
     /// @brief return the type of the JSON value (implicit)
     /// @sa https://json.nlohmann.me/api/basic_json/operator_value_t/
     constexpr operator value_t() const noexcept
     {
-        return m_data.m_type;
+        return m_type;
     }
 
     /// @}
@@ -20632,7 +20571,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     {
         if (JSON_HEDLEY_LIKELY(is_boolean()))
         {
-            return m_data.m_value.boolean;
+            return m_value.boolean;
         }
 
         JSON_THROW(type_error::create(302, detail::concat("type must be boolean, but is ", type_name()), this));
@@ -20641,97 +20580,97 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     /// get a pointer to the value (object)
     object_t* get_impl_ptr(object_t* /*unused*/) noexcept
     {
-        return is_object() ? m_data.m_value.object : nullptr;
+        return is_object() ? m_value.object : nullptr;
     }
 
     /// get a pointer to the value (object)
     constexpr const object_t* get_impl_ptr(const object_t* /*unused*/) const noexcept
     {
-        return is_object() ? m_data.m_value.object : nullptr;
+        return is_object() ? m_value.object : nullptr;
     }
 
     /// get a pointer to the value (array)
     array_t* get_impl_ptr(array_t* /*unused*/) noexcept
     {
-        return is_array() ? m_data.m_value.array : nullptr;
+        return is_array() ? m_value.array : nullptr;
     }
 
     /// get a pointer to the value (array)
     constexpr const array_t* get_impl_ptr(const array_t* /*unused*/) const noexcept
     {
-        return is_array() ? m_data.m_value.array : nullptr;
+        return is_array() ? m_value.array : nullptr;
     }
 
     /// get a pointer to the value (string)
     string_t* get_impl_ptr(string_t* /*unused*/) noexcept
     {
-        return is_string() ? m_data.m_value.string : nullptr;
+        return is_string() ? m_value.string : nullptr;
     }
 
     /// get a pointer to the value (string)
     constexpr const string_t* get_impl_ptr(const string_t* /*unused*/) const noexcept
     {
-        return is_string() ? m_data.m_value.string : nullptr;
+        return is_string() ? m_value.string : nullptr;
     }
 
     /// get a pointer to the value (boolean)
     boolean_t* get_impl_ptr(boolean_t* /*unused*/) noexcept
     {
-        return is_boolean() ? &m_data.m_value.boolean : nullptr;
+        return is_boolean() ? &m_value.boolean : nullptr;
     }
 
     /// get a pointer to the value (boolean)
     constexpr const boolean_t* get_impl_ptr(const boolean_t* /*unused*/) const noexcept
     {
-        return is_boolean() ? &m_data.m_value.boolean : nullptr;
+        return is_boolean() ? &m_value.boolean : nullptr;
     }
 
     /// get a pointer to the value (integer number)
     number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept
     {
-        return is_number_integer() ? &m_data.m_value.number_integer : nullptr;
+        return is_number_integer() ? &m_value.number_integer : nullptr;
     }
 
     /// get a pointer to the value (integer number)
     constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept
     {
-        return is_number_integer() ? &m_data.m_value.number_integer : nullptr;
+        return is_number_integer() ? &m_value.number_integer : nullptr;
     }
 
     /// get a pointer to the value (unsigned number)
     number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept
     {
-        return is_number_unsigned() ? &m_data.m_value.number_unsigned : nullptr;
+        return is_number_unsigned() ? &m_value.number_unsigned : nullptr;
     }
 
     /// get a pointer to the value (unsigned number)
     constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept
     {
-        return is_number_unsigned() ? &m_data.m_value.number_unsigned : nullptr;
+        return is_number_unsigned() ? &m_value.number_unsigned : nullptr;
     }
 
     /// get a pointer to the value (floating-point number)
     number_float_t* get_impl_ptr(number_float_t* /*unused*/) noexcept
     {
-        return is_number_float() ? &m_data.m_value.number_float : nullptr;
+        return is_number_float() ? &m_value.number_float : nullptr;
     }
 
     /// get a pointer to the value (floating-point number)
     constexpr const number_float_t* get_impl_ptr(const number_float_t* /*unused*/) const noexcept
     {
-        return is_number_float() ? &m_data.m_value.number_float : nullptr;
+        return is_number_float() ? &m_value.number_float : nullptr;
     }
 
     /// get a pointer to the value (binary)
     binary_t* get_impl_ptr(binary_t* /*unused*/) noexcept
     {
-        return is_binary() ? m_data.m_value.binary : nullptr;
+        return is_binary() ? m_value.binary : nullptr;
     }
 
     /// get a pointer to the value (binary)
     constexpr const binary_t* get_impl_ptr(const binary_t* /*unused*/) const noexcept
     {
-        return is_binary() ? m_data.m_value.binary : nullptr;
+        return is_binary() ? m_value.binary : nullptr;
     }
 
     /*!
@@ -21114,7 +21053,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
 #if defined(JSON_HAS_CPP_17) && (defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1910 && _MSC_VER <= 1914))
                                                 detail::negation<std::is_same<ValueType, std::string_view>>,
 #endif
-#if defined(JSON_HAS_CPP_17) && JSON_HAS_STATIC_RTTI
+#if defined(JSON_HAS_CPP_17)
                                                 detail::negation<std::is_same<ValueType, std::any>>,
 #endif
                                                 detail::is_detected_lazy<detail::get_template_function, const basic_json_t&, ValueType>
@@ -21151,6 +21090,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
 
     /// @}
 
+
     ////////////////////
     // element access //
     ////////////////////
@@ -21168,7 +21108,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         {
             JSON_TRY
             {
-                return set_parent(m_data.m_value.array->at(idx));
+                return set_parent(m_value.array->at(idx));
             }
             JSON_CATCH (std::out_of_range&)
             {
@@ -21191,7 +21131,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         {
             JSON_TRY
             {
-                return m_data.m_value.array->at(idx);
+                return m_value.array->at(idx);
             }
             JSON_CATCH (std::out_of_range&)
             {
@@ -21215,8 +21155,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
             JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this));
         }
 
-        auto it = m_data.m_value.object->find(key);
-        if (it == m_data.m_value.object->end())
+        auto it = m_value.object->find(key);
+        if (it == m_value.object->end())
         {
             JSON_THROW(out_of_range::create(403, detail::concat("key '", key, "' not found"), this));
         }
@@ -21235,8 +21175,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
             JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this));
         }
 
-        auto it = m_data.m_value.object->find(std::forward<KeyType>(key));
-        if (it == m_data.m_value.object->end())
+        auto it = m_value.object->find(std::forward<KeyType>(key));
+        if (it == m_value.object->end())
         {
             JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward<KeyType>(key)), "' not found"), this));
         }
@@ -21253,8 +21193,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
             JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this));
         }
 
-        auto it = m_data.m_value.object->find(key);
-        if (it == m_data.m_value.object->end())
+        auto it = m_value.object->find(key);
+        if (it == m_value.object->end())
         {
             JSON_THROW(out_of_range::create(403, detail::concat("key '", key, "' not found"), this));
         }
@@ -21273,8 +21213,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
             JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this));
         }
 
-        auto it = m_data.m_value.object->find(std::forward<KeyType>(key));
-        if (it == m_data.m_value.object->end())
+        auto it = m_value.object->find(std::forward<KeyType>(key));
+        if (it == m_value.object->end())
         {
             JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward<KeyType>(key)), "' not found"), this));
         }
@@ -21288,8 +21228,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         // implicitly convert null value to an empty array
         if (is_null())
         {
-            m_data.m_type = value_t::array;
-            m_data.m_value.array = create<array_t>();
+            m_type = value_t::array;
+            m_value.array = create<array_t>();
             assert_invariant();
         }
 
@@ -21297,17 +21237,17 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         if (JSON_HEDLEY_LIKELY(is_array()))
         {
             // fill up array with null values if given idx is outside range
-            if (idx >= m_data.m_value.array->size())
+            if (idx >= m_value.array->size())
             {
 #if JSON_DIAGNOSTICS
                 // remember array size & capacity before resizing
-                const auto old_size = m_data.m_value.array->size();
-                const auto old_capacity = m_data.m_value.array->capacity();
+                const auto old_size = m_value.array->size();
+                const auto old_capacity = m_value.array->capacity();
 #endif
-                m_data.m_value.array->resize(idx + 1);
+                m_value.array->resize(idx + 1);
 
 #if JSON_DIAGNOSTICS
-                if (JSON_HEDLEY_UNLIKELY(m_data.m_value.array->capacity() != old_capacity))
+                if (JSON_HEDLEY_UNLIKELY(m_value.array->capacity() != old_capacity))
                 {
                     // capacity has changed: update all parents
                     set_parents();
@@ -21321,7 +21261,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
                 assert_invariant();
             }
 
-            return m_data.m_value.array->operator[](idx);
+            return m_value.array->operator[](idx);
         }
 
         JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a numeric argument with ", type_name()), this));
@@ -21334,7 +21274,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         // const operator[] only works for arrays
         if (JSON_HEDLEY_LIKELY(is_array()))
         {
-            return m_data.m_value.array->operator[](idx);
+            return m_value.array->operator[](idx);
         }
 
         JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a numeric argument with ", type_name()), this));
@@ -21347,15 +21287,15 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         // implicitly convert null value to an empty object
         if (is_null())
         {
-            m_data.m_type = value_t::object;
-            m_data.m_value.object = create<object_t>();
+            m_type = value_t::object;
+            m_value.object = create<object_t>();
             assert_invariant();
         }
 
         // operator[] only works for objects
         if (JSON_HEDLEY_LIKELY(is_object()))
         {
-            auto result = m_data.m_value.object->emplace(std::move(key), nullptr);
+            auto result = m_value.object->emplace(std::move(key), nullptr);
             return set_parent(result.first->second);
         }
 
@@ -21369,8 +21309,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         // const operator[] only works for objects
         if (JSON_HEDLEY_LIKELY(is_object()))
         {
-            auto it = m_data.m_value.object->find(key);
-            JSON_ASSERT(it != m_data.m_value.object->end());
+            auto it = m_value.object->find(key);
+            JSON_ASSERT(it != m_value.object->end());
             return it->second;
         }
 
@@ -21400,15 +21340,15 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         // implicitly convert null value to an empty object
         if (is_null())
         {
-            m_data.m_type = value_t::object;
-            m_data.m_value.object = create<object_t>();
+            m_type = value_t::object;
+            m_value.object = create<object_t>();
             assert_invariant();
         }
 
         // operator[] only works for objects
         if (JSON_HEDLEY_LIKELY(is_object()))
         {
-            auto result = m_data.m_value.object->emplace(std::forward<KeyType>(key), nullptr);
+            auto result = m_value.object->emplace(std::forward<KeyType>(key), nullptr);
             return set_parent(result.first->second);
         }
 
@@ -21424,8 +21364,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         // const operator[] only works for objects
         if (JSON_HEDLEY_LIKELY(is_object()))
         {
-            auto it = m_data.m_value.object->find(std::forward<KeyType>(key));
-            JSON_ASSERT(it != m_data.m_value.object->end());
+            auto it = m_value.object->find(std::forward<KeyType>(key));
+            JSON_ASSERT(it != m_value.object->end());
             return it->second;
         }
 
@@ -21662,7 +21602,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
 
         IteratorType result = end();
 
-        switch (m_data.m_type)
+        switch (m_type)
         {
             case value_t::boolean:
             case value_t::number_float:
@@ -21679,32 +21619,32 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
                 if (is_string())
                 {
                     AllocatorType<string_t> alloc;
-                    std::allocator_traits<decltype(alloc)>::destroy(alloc, m_data.m_value.string);
-                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_data.m_value.string, 1);
-                    m_data.m_value.string = nullptr;
+                    std::allocator_traits<decltype(alloc)>::destroy(alloc, m_value.string);
+                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.string, 1);
+                    m_value.string = nullptr;
                 }
                 else if (is_binary())
                 {
                     AllocatorType<binary_t> alloc;
-                    std::allocator_traits<decltype(alloc)>::destroy(alloc, m_data.m_value.binary);
-                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_data.m_value.binary, 1);
-                    m_data.m_value.binary = nullptr;
+                    std::allocator_traits<decltype(alloc)>::destroy(alloc, m_value.binary);
+                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.binary, 1);
+                    m_value.binary = nullptr;
                 }
 
-                m_data.m_type = value_t::null;
+                m_type = value_t::null;
                 assert_invariant();
                 break;
             }
 
             case value_t::object:
             {
-                result.m_it.object_iterator = m_data.m_value.object->erase(pos.m_it.object_iterator);
+                result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator);
                 break;
             }
 
             case value_t::array:
             {
-                result.m_it.array_iterator = m_data.m_value.array->erase(pos.m_it.array_iterator);
+                result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator);
                 break;
             }
 
@@ -21732,7 +21672,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
 
         IteratorType result = end();
 
-        switch (m_data.m_type)
+        switch (m_type)
         {
             case value_t::boolean:
             case value_t::number_float:
@@ -21750,33 +21690,33 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
                 if (is_string())
                 {
                     AllocatorType<string_t> alloc;
-                    std::allocator_traits<decltype(alloc)>::destroy(alloc, m_data.m_value.string);
-                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_data.m_value.string, 1);
-                    m_data.m_value.string = nullptr;
+                    std::allocator_traits<decltype(alloc)>::destroy(alloc, m_value.string);
+                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.string, 1);
+                    m_value.string = nullptr;
                 }
                 else if (is_binary())
                 {
                     AllocatorType<binary_t> alloc;
-                    std::allocator_traits<decltype(alloc)>::destroy(alloc, m_data.m_value.binary);
-                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_data.m_value.binary, 1);
-                    m_data.m_value.binary = nullptr;
+                    std::allocator_traits<decltype(alloc)>::destroy(alloc, m_value.binary);
+                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.binary, 1);
+                    m_value.binary = nullptr;
                 }
 
-                m_data.m_type = value_t::null;
+                m_type = value_t::null;
                 assert_invariant();
                 break;
             }
 
             case value_t::object:
             {
-                result.m_it.object_iterator = m_data.m_value.object->erase(first.m_it.object_iterator,
+                result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator,
                                               last.m_it.object_iterator);
                 break;
             }
 
             case value_t::array:
             {
-                result.m_it.array_iterator = m_data.m_value.array->erase(first.m_it.array_iterator,
+                result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator,
                                              last.m_it.array_iterator);
                 break;
             }
@@ -21801,7 +21741,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
             JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this));
         }
 
-        return m_data.m_value.object->erase(std::forward<KeyType>(key));
+        return m_value.object->erase(std::forward<KeyType>(key));
     }
 
     template < typename KeyType, detail::enable_if_t <
@@ -21814,10 +21754,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
             JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this));
         }
 
-        const auto it = m_data.m_value.object->find(std::forward<KeyType>(key));
-        if (it != m_data.m_value.object->end())
+        const auto it = m_value.object->find(std::forward<KeyType>(key));
+        if (it != m_value.object->end())
         {
-            m_data.m_value.object->erase(it);
+            m_value.object->erase(it);
             return 1;
         }
         return 0;
@@ -21855,7 +21795,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
                 JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this));
             }
 
-            m_data.m_value.array->erase(m_data.m_value.array->begin() + static_cast<difference_type>(idx));
+            m_value.array->erase(m_value.array->begin() + static_cast<difference_type>(idx));
         }
         else
         {
@@ -21865,6 +21805,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
 
     /// @}
 
+
     ////////////
     // lookup //
     ////////////
@@ -21880,7 +21821,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
 
         if (is_object())
         {
-            result.m_it.object_iterator = m_data.m_value.object->find(key);
+            result.m_it.object_iterator = m_value.object->find(key);
         }
 
         return result;
@@ -21894,7 +21835,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
 
         if (is_object())
         {
-            result.m_it.object_iterator = m_data.m_value.object->find(key);
+            result.m_it.object_iterator = m_value.object->find(key);
         }
 
         return result;
@@ -21910,7 +21851,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
 
         if (is_object())
         {
-            result.m_it.object_iterator = m_data.m_value.object->find(std::forward<KeyType>(key));
+            result.m_it.object_iterator = m_value.object->find(std::forward<KeyType>(key));
         }
 
         return result;
@@ -21926,7 +21867,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
 
         if (is_object())
         {
-            result.m_it.object_iterator = m_data.m_value.object->find(std::forward<KeyType>(key));
+            result.m_it.object_iterator = m_value.object->find(std::forward<KeyType>(key));
         }
 
         return result;
@@ -21937,7 +21878,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     size_type count(const typename object_t::key_type& key) const
     {
         // return 0 for all nonobject types
-        return is_object() ? m_data.m_value.object->count(key) : 0;
+        return is_object() ? m_value.object->count(key) : 0;
     }
 
     /// @brief returns the number of occurrences of a key in a JSON object
@@ -21947,14 +21888,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     size_type count(KeyType && key) const
     {
         // return 0 for all nonobject types
-        return is_object() ? m_data.m_value.object->count(std::forward<KeyType>(key)) : 0;
+        return is_object() ? m_value.object->count(std::forward<KeyType>(key)) : 0;
     }
 
     /// @brief check the existence of an element in a JSON object
     /// @sa https://json.nlohmann.me/api/basic_json/contains/
     bool contains(const typename object_t::key_type& key) const
     {
-        return is_object() && m_data.m_value.object->find(key) != m_data.m_value.object->end();
+        return is_object() && m_value.object->find(key) != m_value.object->end();
     }
 
     /// @brief check the existence of an element in a JSON object
@@ -21963,7 +21904,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
                  detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>
     bool contains(KeyType && key) const
     {
-        return is_object() && m_data.m_value.object->find(std::forward<KeyType>(key)) != m_data.m_value.object->end();
+        return is_object() && m_value.object->find(std::forward<KeyType>(key)) != m_value.object->end();
     }
 
     /// @brief check the existence of an element in a JSON object given a JSON pointer
@@ -21982,6 +21923,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
 
     /// @}
 
+
     ///////////////
     // iterators //
     ///////////////
@@ -22120,6 +22062,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
 
     /// @}
 
+
     //////////////
     // capacity //
     //////////////
@@ -22131,7 +22074,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     /// @sa https://json.nlohmann.me/api/basic_json/empty/
     bool empty() const noexcept
     {
-        switch (m_data.m_type)
+        switch (m_type)
         {
             case value_t::null:
             {
@@ -22142,13 +22085,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
             case value_t::array:
             {
                 // delegate call to array_t::empty()
-                return m_data.m_value.array->empty();
+                return m_value.array->empty();
             }
 
             case value_t::object:
             {
                 // delegate call to object_t::empty()
-                return m_data.m_value.object->empty();
+                return m_value.object->empty();
             }
 
             case value_t::string:
@@ -22170,7 +22113,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     /// @sa https://json.nlohmann.me/api/basic_json/size/
     size_type size() const noexcept
     {
-        switch (m_data.m_type)
+        switch (m_type)
         {
             case value_t::null:
             {
@@ -22181,13 +22124,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
             case value_t::array:
             {
                 // delegate call to array_t::size()
-                return m_data.m_value.array->size();
+                return m_value.array->size();
             }
 
             case value_t::object:
             {
                 // delegate call to object_t::size()
-                return m_data.m_value.object->size();
+                return m_value.object->size();
             }
 
             case value_t::string:
@@ -22209,18 +22152,18 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     /// @sa https://json.nlohmann.me/api/basic_json/max_size/
     size_type max_size() const noexcept
     {
-        switch (m_data.m_type)
+        switch (m_type)
         {
             case value_t::array:
             {
                 // delegate call to array_t::max_size()
-                return m_data.m_value.array->max_size();
+                return m_value.array->max_size();
             }
 
             case value_t::object:
             {
                 // delegate call to object_t::max_size()
-                return m_data.m_value.object->max_size();
+                return m_value.object->max_size();
             }
 
             case value_t::null:
@@ -22241,6 +22184,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
 
     /// @}
 
+
     ///////////////
     // modifiers //
     ///////////////
@@ -22252,53 +22196,53 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     /// @sa https://json.nlohmann.me/api/basic_json/clear/
     void clear() noexcept
     {
-        switch (m_data.m_type)
+        switch (m_type)
         {
             case value_t::number_integer:
             {
-                m_data.m_value.number_integer = 0;
+                m_value.number_integer = 0;
                 break;
             }
 
             case value_t::number_unsigned:
             {
-                m_data.m_value.number_unsigned = 0;
+                m_value.number_unsigned = 0;
                 break;
             }
 
             case value_t::number_float:
             {
-                m_data.m_value.number_float = 0.0;
+                m_value.number_float = 0.0;
                 break;
             }
 
             case value_t::boolean:
             {
-                m_data.m_value.boolean = false;
+                m_value.boolean = false;
                 break;
             }
 
             case value_t::string:
             {
-                m_data.m_value.string->clear();
+                m_value.string->clear();
                 break;
             }
 
             case value_t::binary:
             {
-                m_data.m_value.binary->clear();
+                m_value.binary->clear();
                 break;
             }
 
             case value_t::array:
             {
-                m_data.m_value.array->clear();
+                m_value.array->clear();
                 break;
             }
 
             case value_t::object:
             {
-                m_data.m_value.object->clear();
+                m_value.object->clear();
                 break;
             }
 
@@ -22322,15 +22266,15 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         // transform null object into an array
         if (is_null())
         {
-            m_data.m_type = value_t::array;
-            m_data.m_value = value_t::array;
+            m_type = value_t::array;
+            m_value = value_t::array;
             assert_invariant();
         }
 
         // add element to array (move semantics)
-        const auto old_capacity = m_data.m_value.array->capacity();
-        m_data.m_value.array->push_back(std::move(val));
-        set_parent(m_data.m_value.array->back(), old_capacity);
+        const auto old_capacity = m_value.array->capacity();
+        m_value.array->push_back(std::move(val));
+        set_parent(m_value.array->back(), old_capacity);
         // if val is moved from, basic_json move constructor marks it null, so we do not call the destructor
     }
 
@@ -22355,15 +22299,15 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         // transform null object into an array
         if (is_null())
         {
-            m_data.m_type = value_t::array;
-            m_data.m_value = value_t::array;
+            m_type = value_t::array;
+            m_value = value_t::array;
             assert_invariant();
         }
 
         // add element to array
-        const auto old_capacity = m_data.m_value.array->capacity();
-        m_data.m_value.array->push_back(val);
-        set_parent(m_data.m_value.array->back(), old_capacity);
+        const auto old_capacity = m_value.array->capacity();
+        m_value.array->push_back(val);
+        set_parent(m_value.array->back(), old_capacity);
     }
 
     /// @brief add an object to an array
@@ -22387,13 +22331,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         // transform null object into an object
         if (is_null())
         {
-            m_data.m_type = value_t::object;
-            m_data.m_value = value_t::object;
+            m_type = value_t::object;
+            m_value = value_t::object;
             assert_invariant();
         }
 
         // add element to object
-        auto res = m_data.m_value.object->insert(val);
+        auto res = m_value.object->insert(val);
         set_parent(res.first->second);
     }
 
@@ -22443,15 +22387,15 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         // transform null object into an array
         if (is_null())
         {
-            m_data.m_type = value_t::array;
-            m_data.m_value = value_t::array;
+            m_type = value_t::array;
+            m_value = value_t::array;
             assert_invariant();
         }
 
         // add element to array (perfect forwarding)
-        const auto old_capacity = m_data.m_value.array->capacity();
-        m_data.m_value.array->emplace_back(std::forward<Args>(args)...);
-        return set_parent(m_data.m_value.array->back(), old_capacity);
+        const auto old_capacity = m_value.array->capacity();
+        m_value.array->emplace_back(std::forward<Args>(args)...);
+        return set_parent(m_value.array->back(), old_capacity);
     }
 
     /// @brief add an object to an object if key does not exist
@@ -22468,13 +22412,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         // transform null object into an object
         if (is_null())
         {
-            m_data.m_type = value_t::object;
-            m_data.m_value = value_t::object;
+            m_type = value_t::object;
+            m_value = value_t::object;
             assert_invariant();
         }
 
         // add element to array (perfect forwarding)
-        auto res = m_data.m_value.object->emplace(std::forward<Args>(args)...);
+        auto res = m_value.object->emplace(std::forward<Args>(args)...);
         set_parent(res.first->second);
 
         // create result iterator and set iterator to the result of emplace
@@ -22492,14 +22436,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     iterator insert_iterator(const_iterator pos, Args&& ... args)
     {
         iterator result(this);
-        JSON_ASSERT(m_data.m_value.array != nullptr);
+        JSON_ASSERT(m_value.array != nullptr);
 
-        auto insert_pos = std::distance(m_data.m_value.array->begin(), pos.m_it.array_iterator);
-        m_data.m_value.array->insert(pos.m_it.array_iterator, std::forward<Args>(args)...);
-        result.m_it.array_iterator = m_data.m_value.array->begin() + insert_pos;
+        auto insert_pos = std::distance(m_value.array->begin(), pos.m_it.array_iterator);
+        m_value.array->insert(pos.m_it.array_iterator, std::forward<Args>(args)...);
+        result.m_it.array_iterator = m_value.array->begin() + insert_pos;
 
         // This could have been written as:
-        // result.m_it.array_iterator = m_data.m_value.array->insert(pos.m_it.array_iterator, cnt, val);
+        // result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val);
         // but the return value of insert is missing in GCC 4.8, so it is written this way instead.
 
         set_parents();
@@ -22626,7 +22570,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
             JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects", this));
         }
 
-        m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator);
+        m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator);
     }
 
     /// @brief updates a JSON object from another object, overwriting existing keys
@@ -22643,8 +22587,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         // implicitly convert null value to an empty object
         if (is_null())
         {
-            m_data.m_type = value_t::object;
-            m_data.m_value.object = create<object_t>();
+            m_type = value_t::object;
+            m_value.object = create<object_t>();
             assert_invariant();
         }
 
@@ -22669,16 +22613,16 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         {
             if (merge_objects && it.value().is_object())
             {
-                auto it2 = m_data.m_value.object->find(it.key());
-                if (it2 != m_data.m_value.object->end())
+                auto it2 = m_value.object->find(it.key());
+                if (it2 != m_value.object->end())
                 {
                     it2->second.update(it.value(), true);
                     continue;
                 }
             }
-            m_data.m_value.object->operator[](it.key()) = it.value();
+            m_value.object->operator[](it.key()) = it.value();
 #if JSON_DIAGNOSTICS
-            m_data.m_value.object->operator[](it.key()).m_parent = this;
+            m_value.object->operator[](it.key()).m_parent = this;
 #endif
         }
     }
@@ -22688,12 +22632,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     void swap(reference other) noexcept (
         std::is_nothrow_move_constructible<value_t>::value&&
         std::is_nothrow_move_assignable<value_t>::value&&
-        std::is_nothrow_move_constructible<json_value>::value&& // NOLINT(cppcoreguidelines-noexcept-swap,performance-noexcept-swap)
+        std::is_nothrow_move_constructible<json_value>::value&&
         std::is_nothrow_move_assignable<json_value>::value
     )
     {
-        std::swap(m_data.m_type, other.m_data.m_type);
-        std::swap(m_data.m_value, other.m_data.m_value);
+        std::swap(m_type, other.m_type);
+        std::swap(m_value, other.m_value);
 
         set_parents();
         other.set_parents();
@@ -22705,7 +22649,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     friend void swap(reference left, reference right) noexcept (
         std::is_nothrow_move_constructible<value_t>::value&&
         std::is_nothrow_move_assignable<value_t>::value&&
-        std::is_nothrow_move_constructible<json_value>::value&& // NOLINT(cppcoreguidelines-noexcept-swap,performance-noexcept-swap)
+        std::is_nothrow_move_constructible<json_value>::value&&
         std::is_nothrow_move_assignable<json_value>::value
     )
     {
@@ -22714,13 +22658,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
 
     /// @brief exchanges the values
     /// @sa https://json.nlohmann.me/api/basic_json/swap/
-    void swap(array_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)
+    void swap(array_t& other) // NOLINT(bugprone-exception-escape)
     {
         // swap only works for arrays
         if (JSON_HEDLEY_LIKELY(is_array()))
         {
             using std::swap;
-            swap(*(m_data.m_value.array), other);
+            swap(*(m_value.array), other);
         }
         else
         {
@@ -22730,13 +22674,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
 
     /// @brief exchanges the values
     /// @sa https://json.nlohmann.me/api/basic_json/swap/
-    void swap(object_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)
+    void swap(object_t& other) // NOLINT(bugprone-exception-escape)
     {
         // swap only works for objects
         if (JSON_HEDLEY_LIKELY(is_object()))
         {
             using std::swap;
-            swap(*(m_data.m_value.object), other);
+            swap(*(m_value.object), other);
         }
         else
         {
@@ -22746,13 +22690,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
 
     /// @brief exchanges the values
     /// @sa https://json.nlohmann.me/api/basic_json/swap/
-    void swap(string_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)
+    void swap(string_t& other) // NOLINT(bugprone-exception-escape)
     {
         // swap only works for strings
         if (JSON_HEDLEY_LIKELY(is_string()))
         {
             using std::swap;
-            swap(*(m_data.m_value.string), other);
+            swap(*(m_value.string), other);
         }
         else
         {
@@ -22762,13 +22706,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
 
     /// @brief exchanges the values
     /// @sa https://json.nlohmann.me/api/basic_json/swap/
-    void swap(binary_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)
+    void swap(binary_t& other) // NOLINT(bugprone-exception-escape)
     {
         // swap only works for strings
         if (JSON_HEDLEY_LIKELY(is_binary()))
         {
             using std::swap;
-            swap(*(m_data.m_value.binary), other);
+            swap(*(m_value.binary), other);
         }
         else
         {
@@ -22784,7 +22728,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         if (JSON_HEDLEY_LIKELY(is_binary()))
         {
             using std::swap;
-            swap(*(m_data.m_value.binary), other);
+            swap(*(m_value.binary), other);
         }
         else
         {
@@ -22812,31 +22756,31 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         switch (lhs_type)                                                                                \
         {                                                                                                \
             case value_t::array:                                                                         \
-                return (*lhs.m_data.m_value.array) op (*rhs.m_data.m_value.array);                                     \
+                return (*lhs.m_value.array) op (*rhs.m_value.array);                                     \
                 \
             case value_t::object:                                                                        \
-                return (*lhs.m_data.m_value.object) op (*rhs.m_data.m_value.object);                                   \
+                return (*lhs.m_value.object) op (*rhs.m_value.object);                                   \
                 \
             case value_t::null:                                                                          \
                 return (null_result);                                                                    \
                 \
             case value_t::string:                                                                        \
-                return (*lhs.m_data.m_value.string) op (*rhs.m_data.m_value.string);                                   \
+                return (*lhs.m_value.string) op (*rhs.m_value.string);                                   \
                 \
             case value_t::boolean:                                                                       \
-                return (lhs.m_data.m_value.boolean) op (rhs.m_data.m_value.boolean);                                   \
+                return (lhs.m_value.boolean) op (rhs.m_value.boolean);                                   \
                 \
             case value_t::number_integer:                                                                \
-                return (lhs.m_data.m_value.number_integer) op (rhs.m_data.m_value.number_integer);                     \
+                return (lhs.m_value.number_integer) op (rhs.m_value.number_integer);                     \
                 \
             case value_t::number_unsigned:                                                               \
-                return (lhs.m_data.m_value.number_unsigned) op (rhs.m_data.m_value.number_unsigned);                   \
+                return (lhs.m_value.number_unsigned) op (rhs.m_value.number_unsigned);                   \
                 \
             case value_t::number_float:                                                                  \
-                return (lhs.m_data.m_value.number_float) op (rhs.m_data.m_value.number_float);                         \
+                return (lhs.m_value.number_float) op (rhs.m_value.number_float);                         \
                 \
             case value_t::binary:                                                                        \
-                return (*lhs.m_data.m_value.binary) op (*rhs.m_data.m_value.binary);                                   \
+                return (*lhs.m_value.binary) op (*rhs.m_value.binary);                                   \
                 \
             case value_t::discarded:                                                                     \
             default:                                                                                     \
@@ -22845,27 +22789,27 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     }                                                                                                    \
     else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float)                   \
     {                                                                                                    \
-        return static_cast<number_float_t>(lhs.m_data.m_value.number_integer) op rhs.m_data.m_value.number_float;      \
+        return static_cast<number_float_t>(lhs.m_value.number_integer) op rhs.m_value.number_float;      \
     }                                                                                                    \
     else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer)                   \
     {                                                                                                    \
-        return lhs.m_data.m_value.number_float op static_cast<number_float_t>(rhs.m_data.m_value.number_integer);      \
+        return lhs.m_value.number_float op static_cast<number_float_t>(rhs.m_value.number_integer);      \
     }                                                                                                    \
     else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float)                  \
     {                                                                                                    \
-        return static_cast<number_float_t>(lhs.m_data.m_value.number_unsigned) op rhs.m_data.m_value.number_float;     \
+        return static_cast<number_float_t>(lhs.m_value.number_unsigned) op rhs.m_value.number_float;     \
     }                                                                                                    \
     else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned)                  \
     {                                                                                                    \
-        return lhs.m_data.m_value.number_float op static_cast<number_float_t>(rhs.m_data.m_value.number_unsigned);     \
+        return lhs.m_value.number_float op static_cast<number_float_t>(rhs.m_value.number_unsigned);     \
     }                                                                                                    \
     else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer)                \
     {                                                                                                    \
-        return static_cast<number_integer_t>(lhs.m_data.m_value.number_unsigned) op rhs.m_data.m_value.number_integer; \
+        return static_cast<number_integer_t>(lhs.m_value.number_unsigned) op rhs.m_value.number_integer; \
     }                                                                                                    \
     else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned)                \
     {                                                                                                    \
-        return lhs.m_data.m_value.number_integer op static_cast<number_integer_t>(rhs.m_data.m_value.number_unsigned); \
+        return lhs.m_value.number_integer op static_cast<number_integer_t>(rhs.m_value.number_unsigned); \
     }                                                                                                    \
     else if(compares_unordered(lhs, rhs))\
     {\
@@ -22882,8 +22826,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     // an operation is computed as an odd number of inverses of others
     static bool compares_unordered(const_reference lhs, const_reference rhs, bool inverse = false) noexcept
     {
-        if ((lhs.is_number_float() && std::isnan(lhs.m_data.m_value.number_float) && rhs.is_number())
-                || (rhs.is_number_float() && std::isnan(rhs.m_data.m_value.number_float) && lhs.is_number()))
+        if ((lhs.is_number_float() && std::isnan(lhs.m_value.number_float) && rhs.is_number())
+                || (rhs.is_number_float() && std::isnan(rhs.m_value.number_float) && lhs.is_number()))
         {
             return true;
         }
@@ -23227,6 +23171,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
 #endif  // JSON_NO_IO
     /// @}
 
+
     /////////////////////
     // deserialization //
     /////////////////////
@@ -23383,7 +23328,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
     JSON_HEDLEY_RETURNS_NON_NULL
     const char* type_name() const noexcept
     {
-        switch (m_data.m_type)
+        switch (m_type)
         {
             case value_t::null:
                 return "null";
@@ -23407,43 +23352,17 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         }
     }
 
+
   JSON_PRIVATE_UNLESS_TESTED:
     //////////////////////
     // member variables //
     //////////////////////
 
-    struct data
-    {
-        /// the type of the current element
-        value_t m_type = value_t::null;
-
-        /// the value of the current element
-        json_value m_value = {};
-
-        data(const value_t v)
-            : m_type(v), m_value(v)
-        {
-        }
-
-        data(size_type cnt, const basic_json& val)
-            : m_type(value_t::array)
-        {
-            m_value.array = create<array_t>(cnt, val);
-        }
-
-        data() noexcept = default;
-        data(data&&) noexcept = default;
-        data(const data&) noexcept = delete;
-        data& operator=(data&&) noexcept = delete;
-        data& operator=(const data&) noexcept = delete;
-
-        ~data() noexcept
-        {
-            m_value.destroy(m_type);
-        }
-    };
+    /// the type of the current element
+    value_t m_type = value_t::null;
 
-    data m_data = {};
+    /// the value of the current element
+    json_value m_value = {};
 
 #if JSON_DIAGNOSTICS
     /// a pointer to a parent value (for debugging purposes)
@@ -23624,6 +23543,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         return from_cbor(ptr, ptr + len, strict, allow_exceptions, tag_handler);
     }
 
+
     JSON_HEDLEY_WARN_UNUSED_RESULT
     JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len))
     static basic_json from_cbor(detail::span_input_adapter&& i,
@@ -23747,6 +23667,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         return res ? result : basic_json(value_t::discarded);
     }
 
+
     /// @brief create a JSON value from an input in BJData format
     /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/
     template<typename InputType>
@@ -23969,7 +23890,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
             }
 
             // make sure the top element of the pointer exists
-            json_pointer const top_pointer = ptr.top();
+            json_pointer top_pointer = ptr.top();
             if (top_pointer != ptr)
             {
                 result.at(top_pointer);
@@ -23981,7 +23902,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
             // parent must exist when performing patch add per RFC6902 specs
             basic_json& parent = result.at(ptr);
 
-            switch (parent.m_data.m_type)
+            switch (parent.m_type)
             {
                 case value_t::null:
                 case value_t::object:
@@ -24027,7 +23948,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
         };
 
         // wrapper for "remove" operation; remove value at ptr
-        const auto operation_remove = [this, & result](json_pointer & ptr)
+        const auto operation_remove = [this, &result](json_pointer & ptr)
         {
             // get reference to parent of JSON pointer ptr
             const auto last_path = ptr.back();
@@ -24070,13 +23991,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
                                           bool string_type) -> basic_json &
             {
                 // find value
-                auto it = val.m_data.m_value.object->find(member);
+                auto it = val.m_value.object->find(member);
 
                 // context-sensitive error message
                 const auto error_msg = (op == "op") ? "operation" : detail::concat("operation '", op, '\'');
 
                 // check if desired value is present
-                if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end()))
+                if (JSON_HEDLEY_UNLIKELY(it == val.m_value.object->end()))
                 {
                     // NOLINTNEXTLINE(performance-inefficient-string-concatenation)
                     JSON_THROW(parse_error::create(105, 0, detail::concat(error_msg, " must have member '", member, "'"), &val));
@@ -24131,7 +24052,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
                     json_pointer from_ptr(from_path);
 
                     // the "from" location must exist - use at()
-                    basic_json const v = result.at(from_ptr);
+                    basic_json v = result.at(from_ptr);
 
                     // The move operation is functionally identical to a
                     // "remove" operation on the "from" location, followed
@@ -24148,7 +24069,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
                     const json_pointer from_ptr(from_path);
 
                     // the "from" location must exist - use at()
-                    basic_json const v = result.at(from_ptr);
+                    basic_json v = result.at(from_ptr);
 
                     // The copy is functionally identical to an "add"
                     // operation at the target location using the value
@@ -24390,11 +24311,7 @@ inline namespace json_literals
 /// @brief user-defined string literal for JSON values
 /// @sa https://json.nlohmann.me/api/basic_json/operator_literal_json/
 JSON_HEDLEY_NON_NULL(1)
-#if !defined(JSON_HEDLEY_GCC_VERSION) || JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0)
-    inline nlohmann::json operator ""_json(const char* s, std::size_t n)
-#else
-    inline nlohmann::json operator "" _json(const char* s, std::size_t n)
-#endif
+inline nlohmann::json operator "" _json(const char* s, std::size_t n)
 {
     return nlohmann::json::parse(s, s + n);
 }
@@ -24402,11 +24319,7 @@ JSON_HEDLEY_NON_NULL(1)
 /// @brief user-defined string literal for JSON pointer
 /// @sa https://json.nlohmann.me/api/basic_json/operator_literal_json_pointer/
 JSON_HEDLEY_NON_NULL(1)
-#if !defined(JSON_HEDLEY_GCC_VERSION) || JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0)
-    inline nlohmann::json::json_pointer operator ""_json_pointer(const char* s, std::size_t n)
-#else
-    inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n)
-#endif
+inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n)
 {
     return nlohmann::json::json_pointer(std::string(s, n));
 }
@@ -24425,7 +24338,7 @@ namespace std // NOLINT(cert-dcl58-cpp)
 /// @brief hash value for JSON objects
 /// @sa https://json.nlohmann.me/api/basic_json/std_hash/
 NLOHMANN_BASIC_JSON_TPL_DECLARATION
-struct hash<nlohmann::NLOHMANN_BASIC_JSON_TPL> // NOLINT(cert-dcl58-cpp)
+struct hash<nlohmann::NLOHMANN_BASIC_JSON_TPL>
 {
     std::size_t operator()(const nlohmann::NLOHMANN_BASIC_JSON_TPL& j) const
     {
@@ -24458,8 +24371,8 @@ struct less< ::nlohmann::detail::value_t> // do not remove the space after '<',
 /// @brief exchanges the values of two JSON objects
 /// @sa https://json.nlohmann.me/api/basic_json/std_swap/
 NLOHMANN_BASIC_JSON_TPL_DECLARATION
-inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC_JSON_TPL& j2) noexcept(  // NOLINT(readability-inconsistent-declaration-parameter-name, cert-dcl58-cpp)
-    is_nothrow_move_constructible<nlohmann::NLOHMANN_BASIC_JSON_TPL>::value&&                          // NOLINT(misc-redundant-expression,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)
+inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC_JSON_TPL& j2) noexcept(  // NOLINT(readability-inconsistent-declaration-parameter-name)
+    is_nothrow_move_constructible<nlohmann::NLOHMANN_BASIC_JSON_TPL>::value&&                          // NOLINT(misc-redundant-expression)
     is_nothrow_move_assignable<nlohmann::NLOHMANN_BASIC_JSON_TPL>::value)
 {
     j1.swap(j2);
@@ -24470,13 +24383,8 @@ inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC
 }  // namespace std
 
 #if JSON_USE_GLOBAL_UDLS
-    #if !defined(JSON_HEDLEY_GCC_VERSION) || JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0)
-        using nlohmann::literals::json_literals::operator ""_json; // NOLINT(misc-unused-using-decls,google-global-names-in-headers)
-        using nlohmann::literals::json_literals::operator ""_json_pointer; //NOLINT(misc-unused-using-decls,google-global-names-in-headers)
-    #else
-        using nlohmann::literals::json_literals::operator "" _json; // NOLINT(misc-unused-using-decls,google-global-names-in-headers)
-        using nlohmann::literals::json_literals::operator "" _json_pointer; //NOLINT(misc-unused-using-decls,google-global-names-in-headers)
-    #endif
+    using nlohmann::literals::json_literals::operator "" _json; // NOLINT(misc-unused-using-decls,google-global-names-in-headers)
+    using nlohmann::literals::json_literals::operator "" _json_pointer; //NOLINT(misc-unused-using-decls,google-global-names-in-headers)
 #endif
 
 // #include <nlohmann/detail/macro_unscope.hpp>
@@ -24520,7 +24428,6 @@ inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC
     #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM
     #undef JSON_HAS_THREE_WAY_COMPARISON
     #undef JSON_HAS_RANGES
-    #undef JSON_HAS_STATIC_RTTI
     #undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
 #endif
 
diff --git a/app/src/main/cpp/libcxx b/app/src/main/cpp/libcxx
new file mode 160000
index 0000000000000000000000000000000000000000..12c8f4e93f196a700137e983dcceeac43cf807f2
--- /dev/null
+++ b/app/src/main/cpp/libcxx
@@ -0,0 +1 @@
+Subproject commit 12c8f4e93f196a700137e983dcceeac43cf807f2
diff --git a/app/src/main/cpp/main.cpp b/app/src/main/cpp/main.cpp
index b902e433aa091b8fa4bc0e89317202f5b19d28b3..034ad45f78379ac94c86ae6ea7f47ad459fdb3d1 100644
--- a/app/src/main/cpp/main.cpp
+++ b/app/src/main/cpp/main.cpp
@@ -4,15 +4,15 @@
 #include <fstream>
 
 #include "zygisk.hpp"
-#include "dobby.h"
+#include "shadowhook.h"
 #include "json.hpp"
 
+#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "PIF/Native", __VA_ARGS__)
+
 #define DEX_FILE_PATH "/data/adb/modules/playintegrityfix/classes.dex"
 
 #define PROP_FILE_PATH "/data/adb/modules/playintegrityfix/pif.json"
 
-#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "PIF/Native", __VA_ARGS__)
-
 static std::string FIRST_API_LEVEL, SECURITY_PATCH;
 
 typedef void (*T_Callback)(void *, const char *, const char *, uint32_t);
@@ -58,14 +58,39 @@ my_system_property_read_callback(const prop_info *pi, T_Callback callback, void
 }
 
 static void doHook() {
-    void *handle = DobbySymbolResolver("libc.so", "__system_property_read_callback");
+    shadowhook_init(SHADOWHOOK_MODE_UNIQUE, false);
+    void *handle = shadowhook_hook_sym_name(
+            "libc.so",
+            "__system_property_read_callback",
+            reinterpret_cast<void *>(my_system_property_read_callback),
+            reinterpret_cast<void **>(&o_system_property_read_callback)
+    );
     if (handle == nullptr) {
         LOGD("Couldn't find '__system_property_read_callback' handle. Report to @chiteroman");
         return;
     }
     LOGD("Found '__system_property_read_callback' handle at %p", handle);
-    DobbyHook(handle, reinterpret_cast<void *>(my_system_property_read_callback),
-              reinterpret_cast<void **>(&o_system_property_read_callback));
+}
+
+static void sendVector(int fd, const std::vector<char> &vec) {
+    // Send the size of the vector
+    size_t size = vec.size();
+    write(fd, &size, sizeof(size_t));
+
+    // Send the vector data
+    write(fd, vec.data(), size);
+}
+
+static std::vector<char> receiveVector(int fd) {
+    // Receive the size of the vector
+    size_t size;
+    read(fd, &size, sizeof(size_t));
+
+    // Receive the vector data
+    std::vector<char> vec(size);
+    read(fd, vec.data(), size);
+
+    return vec;
 }
 
 class PlayIntegrityFix : public zygisk::ModuleBase {
@@ -99,47 +124,32 @@ public:
             return;
         }
 
-        ssize_t size;
-        char buffer[10000];
         int fd = api->connectCompanion();
 
-        size = read(fd, buffer, sizeof(buffer));
-
-        if (size > 0) {
-            moduleDex.insert(moduleDex.end(), buffer, buffer + size);
-        } else {
-            LOGD("Couldn't load classes.dex file in memory");
-            close(fd);
-            api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
-            return;
-        }
-
-        lseek(fd, 0, SEEK_SET);
-
-        size = read(fd, buffer, sizeof(buffer));
-
-        if (size > 0) {
-            jsonStr.insert(jsonStr.end(), buffer, buffer + size);
-        } else {
-            LOGD("Couldn't load pif.json file in memory");
-        }
+        dexVector = receiveVector(fd);
+        propVector = receiveVector(fd);
 
         close(fd);
 
-        LOGD("Received 'classes.dex' file from socket: %d bytes",
-             static_cast<int>(moduleDex.size()));
+        LOGD("Read from file descriptor file 'classes.dex' -> %d bytes",
+             static_cast<int>(dexVector.size()));
+        LOGD("Read from file descriptor file 'pif.json' -> %d bytes",
+             static_cast<int>(propVector.size()));
 
-        LOGD("Received 'pif.json' file from socket: %d bytes", static_cast<int>(jsonStr.size()));
+        if (dexVector.empty() || propVector.empty()) api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
     }
 
     void postAppSpecialize(const zygisk::AppSpecializeArgs *args) override {
-        if (moduleDex.empty()) return;
+        if (dexVector.empty() || propVector.empty()) return;
 
         readJson();
 
         inject();
 
         doHook();
+
+        dexVector.clear();
+        propVector.clear();
     }
 
     void preServerSpecialize(zygisk::ServerSpecializeArgs *args) override {
@@ -149,11 +159,11 @@ public:
 private:
     zygisk::Api *api = nullptr;
     JNIEnv *env = nullptr;
-    std::vector<char> moduleDex;
-    std::string jsonStr;
+    std::vector<char> dexVector, propVector;
 
     void readJson() {
-        nlohmann::json json = nlohmann::json::parse(jsonStr, nullptr, false);
+        std::string data(propVector.cbegin(), propVector.cend());
+        nlohmann::json json = nlohmann::json::parse(data, nullptr, false, true);
 
         auto getStringFromJson = [&json](const std::string &key) {
             return json.contains(key) && !json[key].is_null() ? json[key].get<std::string>()
@@ -162,8 +172,6 @@ private:
 
         SECURITY_PATCH = getStringFromJson("SECURITY_PATCH");
         FIRST_API_LEVEL = getStringFromJson("FIRST_API_LEVEL");
-
-        json.clear();
     }
 
     void inject() {
@@ -177,8 +185,8 @@ private:
         auto dexClClass = env->FindClass("dalvik/system/InMemoryDexClassLoader");
         auto dexClInit = env->GetMethodID(dexClClass, "<init>",
                                           "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
-        auto buffer = env->NewDirectByteBuffer(moduleDex.data(),
-                                               static_cast<jlong>(moduleDex.size()));
+        auto buffer = env->NewDirectByteBuffer(dexVector.data(),
+                                               static_cast<jlong>(dexVector.size()));
         auto dexCl = env->NewObject(dexClClass, dexClInit, buffer, systemClassLoader);
 
         LOGD("load class");
@@ -192,15 +200,13 @@ private:
         LOGD("read json");
         auto readProps = env->GetStaticMethodID(entryClass, "readJson",
                                                 "(Ljava/lang/String;)V");
-        auto javaStr = env->NewStringUTF(jsonStr.c_str());
+        std::string data(propVector.cbegin(), propVector.cend());
+        auto javaStr = env->NewStringUTF(data.c_str());
         env->CallStaticVoidMethod(entryClass, readProps, javaStr);
 
         LOGD("call init");
         auto entryInit = env->GetStaticMethodID(entryClass, "init", "()V");
         env->CallStaticVoidMethod(entryClass, entryInit);
-
-        moduleDex.clear();
-        jsonStr.clear();
     }
 };
 
@@ -210,13 +216,17 @@ static void companion(int fd) {
 
     std::vector<char> dexVector((std::istreambuf_iterator<char>(dex)),
                                 std::istreambuf_iterator<char>());
-
     std::vector<char> propVector((std::istreambuf_iterator<char>(prop)),
                                  std::istreambuf_iterator<char>());
 
-    write(fd, dexVector.data(), dexVector.size());
-    lseek(fd, 0, SEEK_SET);
-    write(fd, propVector.data(), propVector.size());
+    dex.close();
+    prop.close();
+
+    sendVector(fd, dexVector);
+    sendVector(fd, propVector);
+
+    dexVector.clear();
+    propVector.clear();
 }
 
 REGISTER_ZYGISK_MODULE(PlayIntegrityFix)
diff --git a/app/src/main/cpp/shadowhook/arch/arm/sh_a32.c b/app/src/main/cpp/shadowhook/arch/arm/sh_a32.c
new file mode 100644
index 0000000000000000000000000000000000000000..d71e4f88970a782a5fc038d98b99dc0537bd3dd6
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/arch/arm/sh_a32.c
@@ -0,0 +1,446 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#include "sh_a32.h"
+
+#include <inttypes.h>
+#include <sh_util.h>
+#include <stdint.h>
+
+#include "sh_log.h"
+
+// https://developer.arm.com/documentation/ddi0406/latest
+// https://developer.arm.com/documentation/ddi0597/latest
+
+typedef enum {
+  IGNORED = 0,
+  B_A1,
+  BX_A1,
+  BL_IMM_A1,
+  BLX_IMM_A2,
+  ADD_REG_A1,
+  ADD_REG_PC_A1,
+  SUB_REG_A1,
+  SUB_REG_PC_A1,
+  ADR_A1,
+  ADR_A2,
+  MOV_REG_A1,
+  MOV_REG_PC_A1,
+  LDR_LIT_A1,
+  LDR_LIT_PC_A1,
+  LDRB_LIT_A1,
+  LDRD_LIT_A1,
+  LDRH_LIT_A1,
+  LDRSB_LIT_A1,
+  LDRSH_LIT_A1,
+  LDR_REG_A1,
+  LDR_REG_PC_A1,
+  LDRB_REG_A1,
+  LDRD_REG_A1,
+  LDRH_REG_A1,
+  LDRSB_REG_A1,
+  LDRSH_REG_A1
+} sh_a32_type_t;
+
+static sh_a32_type_t sh_a32_get_type(uint32_t inst) {
+  if (((inst & 0x0F000000u) == 0x0A000000) && ((inst & 0xF0000000) != 0xF0000000))
+    return B_A1;
+  else if (((inst & 0x0FFFFFFFu) == 0x012FFF1F) && ((inst & 0xF0000000) != 0xF0000000))
+    return BX_A1;
+  else if (((inst & 0x0F000000u) == 0x0B000000) && ((inst & 0xF0000000) != 0xF0000000))
+    return BL_IMM_A1;
+  else if ((inst & 0xFE000000) == 0xFA000000)
+    return BLX_IMM_A2;
+  else if (((inst & 0x0FE00010u) == 0x00800000) && ((inst & 0xF0000000) != 0xF0000000) &&
+           ((inst & 0x0010F000u) != 0x0010F000) && ((inst & 0x000F0000u) != 0x000D0000) &&
+           (((inst & 0x000F0000u) == 0x000F0000) || ((inst & 0x0000000Fu) == 0x0000000F)))
+    return ((inst & 0x0000F000u) == 0x0000F000) ? ADD_REG_PC_A1 : ADD_REG_A1;
+  else if (((inst & 0x0FE00010u) == 0x00400000) && ((inst & 0xF0000000) != 0xF0000000) &&
+           ((inst & 0x0010F000u) != 0x0010F000) && ((inst & 0x000F0000u) != 0x000D0000) &&
+           (((inst & 0x000F0000u) == 0x000F0000) || ((inst & 0x0000000Fu) == 0x0000000F)))
+    return ((inst & 0x0000F000u) == 0x0000F000) ? SUB_REG_PC_A1 : SUB_REG_A1;
+  else if (((inst & 0x0FFF0000u) == 0x028F0000) && ((inst & 0xF0000000) != 0xF0000000))
+    return ADR_A1;
+  else if (((inst & 0x0FFF0000u) == 0x024F0000) && ((inst & 0xF0000000) != 0xF0000000))
+    return ADR_A2;
+  else if (((inst & 0x0FEF001Fu) == 0x01A0000F) && ((inst & 0xF0000000) != 0xF0000000) &&
+           ((inst & 0x0010F000u) != 0x0010F000) &&
+           (!(((inst & 0x0000F000u) == 0x0000F000) && ((inst & 0x00000FF0u) != 0x00000000))))
+    return ((inst & 0x0000F000u) == 0x0000F000) ? MOV_REG_PC_A1 : MOV_REG_A1;
+  else if (((inst & 0x0F7F0000u) == 0x051F0000) && ((inst & 0xF0000000) != 0xF0000000))
+    return ((inst & 0x0000F000u) == 0x0000F000) ? LDR_LIT_PC_A1 : LDR_LIT_A1;
+  else if (((inst & 0x0F7F0000u) == 0x055F0000) && ((inst & 0xF0000000) != 0xF0000000))
+    return LDRB_LIT_A1;
+  else if (((inst & 0x0F7F00F0u) == 0x014F00D0) && ((inst & 0xF0000000) != 0xF0000000))
+    return LDRD_LIT_A1;
+  else if (((inst & 0x0F7F00F0u) == 0x015F00B0) && ((inst & 0xF0000000) != 0xF0000000))
+    return LDRH_LIT_A1;
+  else if (((inst & 0x0F7F00F0u) == 0x015F00D0) && ((inst & 0xF0000000) != 0xF0000000))
+    return LDRSB_LIT_A1;
+  else if (((inst & 0x0F7F00F0u) == 0x015F00F0) && ((inst & 0xF0000000) != 0xF0000000))
+    return LDRSH_LIT_A1;
+  else if (((inst & 0x0E5F0010u) == 0x061F0000) && ((inst & 0xF0000000) != 0xF0000000) &&
+           ((inst & 0x01200000u) != 0x00200000))
+    return ((inst & 0x0000F000u) == 0x0000F000) ? LDR_REG_PC_A1 : LDR_REG_A1;
+  else if (((inst & 0x0E5F0010u) == 0x065F0000) && ((inst & 0xF0000000) != 0xF0000000) &&
+           ((inst & 0x01200000u) != 0x00200000))
+    return LDRB_REG_A1;
+  else if (((inst & 0x0E5F0FF0u) == 0x000F00D0) && ((inst & 0xF0000000) != 0xF0000000) &&
+           ((inst & 0x01200000u) != 0x00200000))
+    return LDRD_REG_A1;
+  else if (((inst & 0x0E5F0FF0u) == 0x001F00B0) && ((inst & 0xF0000000) != 0xF0000000) &&
+           ((inst & 0x01200000u) != 0x00200000))
+    return LDRH_REG_A1;
+  else if (((inst & 0x0E5F0FF0u) == 0x001F00D0) && ((inst & 0xF0000000) != 0xF0000000) &&
+           ((inst & 0x01200000u) != 0x00200000))
+    return LDRSB_REG_A1;
+  else if (((inst & 0x0E5F0FF0u) == 0x001F00F0) && ((inst & 0xF0000000) != 0xF0000000) &&
+           ((inst & 0x01200000u) != 0x00200000))
+    return LDRSH_REG_A1;
+  else
+    return IGNORED;
+}
+
+size_t sh_a32_get_rewrite_inst_len(uint32_t inst) {
+  static uint8_t map[] = {
+      4,   // IGNORED
+      12,  // B_A1
+      12,  // BX_A1
+      16,  // BL_IMM_A1
+      16,  // BLX_IMM_A2
+      32,  // ADD_REG_A1
+      32,  // ADD_REG_PC_A1
+      32,  // SUB_REG_A1
+      32,  // SUB_REG_PC_A1
+      12,  // ADR_A1
+      12,  // ADR_A2
+      32,  // MOV_REG_A1
+      12,  // MOV_REG_PC_A1
+      24,  // LDR_LIT_A1
+      36,  // LDR_LIT_PC_A1
+      24,  // LDRB_LIT_A1
+      24,  // LDRD_LIT_A1
+      24,  // LDRH_LIT_A1
+      24,  // LDRSB_LIT_A1
+      24,  // LDRSH_LIT_A1
+      32,  // LDR_REG_A1
+      36,  // LDR_REG_PC_A1
+      32,  // LDRB_REG_A1
+      32,  // LDRD_REG_A1
+      32,  // LDRH_REG_A1
+      32,  // LDRSB_REG_A1
+      32   // LDRSH_REG_A1
+  };
+
+  return (size_t)(map[sh_a32_get_type(inst)]);
+}
+
+static bool sh_a32_is_addr_need_fix(uintptr_t addr, sh_a32_rewrite_info_t *rinfo) {
+  return (rinfo->overwrite_start_addr <= addr && addr < rinfo->overwrite_end_addr);
+}
+
+static uintptr_t sh_a32_fix_addr(uintptr_t addr, sh_a32_rewrite_info_t *rinfo) {
+  if (rinfo->overwrite_start_addr <= addr && addr < rinfo->overwrite_end_addr) {
+    uintptr_t cursor_addr = rinfo->overwrite_start_addr;
+    size_t offset = 0;
+    for (size_t i = 0; i < rinfo->rewrite_inst_lens_cnt; i++) {
+      if (cursor_addr >= addr) break;
+      cursor_addr += 4;
+      offset += rinfo->rewrite_inst_lens[i];
+    }
+    uintptr_t fixed_addr = (uintptr_t)rinfo->rewrite_buf + offset;
+    SH_LOG_INFO("a32 rewrite: fix addr %" PRIxPTR " -> %" PRIxPTR, addr, fixed_addr);
+    return fixed_addr;
+  }
+
+  return addr;
+}
+
+static size_t sh_a32_rewrite_b(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a32_type_t type,
+                               sh_a32_rewrite_info_t *rinfo) {
+  uint32_t cond;
+  if (type == B_A1 || type == BL_IMM_A1 || type == BX_A1)
+    cond = SH_UTIL_GET_BITS_32(inst, 31, 28);
+  else
+    // type == BLX_IMM_A2
+    cond = 0xE;  // 1110 None (AL)
+
+  uint32_t addr;
+  if (type == B_A1 || type == BL_IMM_A1) {
+    uint32_t imm24 = SH_UTIL_GET_BITS_32(inst, 23, 0);
+    uint32_t imm32 = SH_UTIL_SIGN_EXTEND_32(imm24 << 2u, 26u);
+    addr = pc + imm32;  // arm -> arm
+  } else if (type == BLX_IMM_A2) {
+    uint32_t h = SH_UTIL_GET_BIT_32(inst, 24);
+    uint32_t imm24 = SH_UTIL_GET_BITS_32(inst, 23, 0);
+    uint32_t imm32 = SH_UTIL_SIGN_EXTEND_32((imm24 << 2u) | (h << 1u), 26u);
+    addr = SH_UTIL_SET_BIT0(pc + imm32);  // arm -> thumb
+  } else {
+    // type == BX_A1
+    // BX PC
+    // PC must be even, and the "arm" instruction must be at a 4-byte aligned address,
+    // so the instruction set must keep "arm" unchanged.
+    addr = pc;  // arm -> arm
+  }
+  addr = sh_a32_fix_addr(addr, rinfo);
+
+  size_t idx = 0;
+  if (type == BL_IMM_A1 || type == BLX_IMM_A2) {
+    buf[idx++] = 0x028FE008u | (cond << 28u);  // ADD<c> LR, PC, #8
+  }
+  buf[idx++] = 0x059FF000u | (cond << 28u);  // LDR<c> PC, [PC, #0]
+  buf[idx++] = 0xEA000000;                   // B #0
+  buf[idx++] = addr;
+  return idx * 4;  // 12 or 16
+}
+
+static size_t sh_a32_rewrite_add_or_sub(uint32_t *buf, uint32_t inst, uintptr_t pc) {
+  // ADD{S}<c> <Rd>, <Rn>, PC{, <shift>}  or  ADD{S}<c> <Rd>, PC, <Rm>{, <shift>}
+  // SUB{S}<c> <Rd>, <Rn>, PC{, <shift>}  or  SUB{S}<c> <Rd>, PC, <Rm>{, <shift>}
+  uint32_t cond = SH_UTIL_GET_BITS_32(inst, 31, 28);
+  uint32_t rn = SH_UTIL_GET_BITS_32(inst, 19, 16);
+  uint32_t rm = SH_UTIL_GET_BITS_32(inst, 3, 0);
+  uint32_t rd = SH_UTIL_GET_BITS_32(inst, 15, 12);
+
+  uint32_t rx;  // r0 - r3
+  for (rx = 3;; --rx)
+    if (rx != rn && rx != rm && rx != rd) break;
+
+  if (rd == 0xF)  // Rd == PC
+  {
+    uint32_t ry;  // r0 - r4
+    for (ry = 4;; --ry)
+      if (ry != rn && ry != rm && ry != rd && ry != rx) break;
+
+    buf[0] = 0x0A000000u | (cond << 28u);           // B<c> #0
+    buf[1] = 0xEA000005;                            // B #20
+    buf[2] = 0xE92D8000 | (1u << rx) | (1u << ry);  // PUSH {Rx, Ry, PC}
+    buf[3] = 0xE59F0008 | (rx << 12u);              // LDR Rx, [PC, #8]
+    if (rn == 0xF)
+      // Rn == PC
+      buf[4] =
+          (inst & 0x0FF00FFFu) | 0xE0000000 | (ry << 12u) | (rx << 16u);  // ADD/SUB Ry, Rx, Rm{, <shift>}
+    else
+      // Rm == PC
+      buf[4] = (inst & 0x0FFF0FF0u) | 0xE0000000 | (ry << 12u) | rx;  // ADD/SUB Ry, Rn, Rx{, <shift>}
+    buf[5] = 0xE58D0008 | (ry << 12u);                                // STR Ry, [SP, #8]
+    buf[6] = 0xE8BD8000 | (1u << rx) | (1u << ry);                    // POP {Rx, Ry, PC}
+    buf[7] = pc;
+    return 32;
+  } else {
+    buf[0] = 0x0A000000u | (cond << 28u);  // B<c> #0
+    buf[1] = 0xEA000005;                   // B #20
+    buf[2] = 0xE52D0004 | (rx << 12u);     // PUSH {Rx}
+    buf[3] = 0xE59F0008 | (rx << 12u);     // LDR Rx, [PC, #8]
+    if (rn == 0xF)
+      // Rn == PC
+      buf[4] = (inst & 0x0FF0FFFFu) | 0xE0000000 | (rx << 16u);  // ADD/SUB{S} Rd, Rx, Rm{, <shift>}
+    else
+      // Rm == PC
+      buf[4] = (inst & 0x0FFFFFF0u) | 0xE0000000 | rx;  // ADD/SUB{S} Rd, Rn, Rx{, <shift>}
+    buf[5] = 0xE49D0004 | (rx << 12u);                  // POP {Rx}
+    buf[6] = 0xEA000000;                                // B #0
+    buf[7] = pc;
+    return 32;
+  }
+}
+
+static size_t sh_a32_rewrite_adr(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a32_type_t type,
+                                 sh_a32_rewrite_info_t *rinfo) {
+  uint32_t cond = SH_UTIL_GET_BITS_32(inst, 31, 28);
+  uint32_t rd = SH_UTIL_GET_BITS_32(inst, 15, 12);  // r0 - r15
+  uint32_t imm12 = SH_UTIL_GET_BITS_32(inst, 11, 0);
+  uint32_t imm32 = sh_util_arm_expand_imm(imm12);
+  uint32_t addr = (type == ADR_A1 ? (SH_UTIL_ALIGN_4(pc) + imm32) : (SH_UTIL_ALIGN_4(pc) - imm32));
+  if (sh_a32_is_addr_need_fix(addr, rinfo)) return 0;  // rewrite failed
+
+  buf[0] = 0x059F0000u | (cond << 28u) | (rd << 12u);  // LDR<c> Rd, [PC, #0]
+  buf[1] = 0xEA000000;                                 // B #0
+  buf[2] = addr;
+  return 12;
+}
+
+static size_t sh_a32_rewrite_mov(uint32_t *buf, uint32_t inst, uintptr_t pc) {
+  // MOV{S}<c> <Rd>, PC
+  uint32_t cond = SH_UTIL_GET_BITS_32(inst, 31, 28);
+  uint32_t rd = SH_UTIL_GET_BITS_32(inst, 15, 12);
+  uint32_t rx = (rd == 0) ? 1 : 0;
+
+  if (rd == 0xF)  // Rd == PC (MOV PC, PC)
+  {
+    buf[0] = 0x059FF000u | (cond << 28u);  // LDR<c> PC, [PC, #0]
+    buf[1] = 0xEA000000;                   // B #0
+    buf[2] = pc;
+    return 12;
+  } else {
+    buf[0] = 0x0A000000u | (cond << 28u);             // B<c> #0
+    buf[1] = 0xEA000005;                              // B #20
+    buf[2] = 0xE52D0004 | (rx << 12u);                // PUSH {Rx}
+    buf[3] = 0xE59F0008 | (rx << 12u);                // LDR Rx, [PC, #8]
+    buf[4] = (inst & 0x0FFFFFF0u) | 0xE0000000 | rx;  // MOV{S} Rd, Rx{, <shift> #<amount>/RRX}
+    buf[5] = 0xE49D0004 | (rx << 12u);                // POP {Rx}
+    buf[6] = 0xEA000000;                              // B #0
+    buf[7] = pc;
+    return 32;
+  }
+}
+
+static size_t sh_a32_rewrite_ldr_lit(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a32_type_t type,
+                                     sh_a32_rewrite_info_t *rinfo) {
+  uint32_t cond = SH_UTIL_GET_BITS_32(inst, 31, 28);
+  uint32_t u = SH_UTIL_GET_BIT_32(inst, 23);
+  uint32_t rt = SH_UTIL_GET_BITS_16(inst, 15, 12);
+
+  uint32_t imm32;
+  if (type == LDR_LIT_A1 || type == LDR_LIT_PC_A1 || type == LDRB_LIT_A1)
+    imm32 = SH_UTIL_GET_BITS_32(inst, 11, 0);
+  else
+    imm32 = (SH_UTIL_GET_BITS_32(inst, 11, 8) << 4u) + SH_UTIL_GET_BITS_32(inst, 3, 0);
+  uint32_t addr = (u ? (SH_UTIL_ALIGN_4(pc) + imm32) : (SH_UTIL_ALIGN_4(pc) - imm32));
+  if (sh_a32_is_addr_need_fix(addr, rinfo)) return 0;  // rewrite failed
+
+  if (type == LDR_LIT_PC_A1 && rt == 0xF) {
+    // Rt == PC
+    buf[0] = 0x0A000000u | (cond << 28u);  // B<c> #0
+    buf[1] = 0xEA000006;                   // B #24
+    buf[2] = 0xE92D0003;                   // PUSH {R0, R1}
+    buf[3] = 0xE59F0000;                   // LDR R0, [PC, #0]
+    buf[4] = 0xEA000000;                   // B #0
+    buf[5] = addr;                         //
+    buf[6] = 0xE5900000;                   // LDR R0, [R0]
+    buf[7] = 0xE58D0004;                   // STR R0, [SP, #4]
+    buf[8] = 0xE8BD8001;                   // POP {R0, PC}
+    return 36;
+  } else {
+    buf[0] = 0x0A000000u | (cond << 28u);  // B<c> #0
+    buf[1] = 0xEA000003;                   // B #12
+    buf[2] = 0xE59F0000 | (rt << 12u);     // LDR Rt, [PC, #0]
+    buf[3] = 0xEA000000;                   // B #0
+    buf[4] = addr;                         //
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wswitch"
+    switch (type) {
+      case LDR_LIT_A1:
+        buf[5] = 0xE5900000 | (rt << 16u) | (rt << 12u);  // LDR Rt, [Rt]
+        break;
+      case LDRB_LIT_A1:
+        buf[5] = 0xE5D00000 | (rt << 16u) | (rt << 12u);  // LDRB Rt, [Rt]
+        break;
+      case LDRD_LIT_A1:
+        buf[5] = 0xE1C000D0 | (rt << 16u) | (rt << 12u);  // LDRD Rt, [Rt]
+        break;
+      case LDRH_LIT_A1:
+        buf[5] = 0xE1D000B0 | (rt << 16u) | (rt << 12u);  // LDRH Rt, [Rt]
+        break;
+      case LDRSB_LIT_A1:
+        buf[5] = 0xE1D000D0 | (rt << 16u) | (rt << 12u);  // LDRSB Rt, [Rt]
+        break;
+      case LDRSH_LIT_A1:
+        buf[5] = 0xE1D000F0 | (rt << 16u) | (rt << 12u);  // LDRSH Rt, [Rt]
+        break;
+    }
+#pragma clang diagnostic pop
+    return 24;
+  }
+}
+
+static size_t sh_a32_rewrite_ldr_reg(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a32_type_t type) {
+  // LDR<c> <Rt>, [PC,+/-<Rm>{, <shift>}]{!}
+  // ......
+  uint32_t cond = SH_UTIL_GET_BITS_32(inst, 31, 28);
+  uint32_t rt = SH_UTIL_GET_BITS_16(inst, 15, 12);
+  uint32_t rt2 = rt + 1;
+  uint32_t rm = SH_UTIL_GET_BITS_16(inst, 3, 0);
+  uint32_t rx;  // r0 - r3
+  for (rx = 3;; --rx)
+    if (rx != rt && rx != rt2 && rx != rm) break;
+
+  if (type == LDR_REG_PC_A1 && rt == 0xF) {
+    // Rt == PC
+    uint32_t ry;  // r0 - r4
+    for (ry = 4;; --ry)
+      if (ry != rt && ry != rt2 && ry != rm && ry != rx) break;
+
+    buf[0] = 0x0A000000u | (cond << 28u);           // B<c> #0
+    buf[1] = 0xEA000006;                            // B #24
+    buf[2] = 0xE92D8000 | (1u << rx) | (1u << ry);  // PUSH {Rx, Ry, PC}
+    buf[3] = 0xE59F0000 | (rx << 12u);              // LDR Rx, [PC, #8]
+    buf[4] = 0xEA000000;                            // B #0
+    buf[5] = pc;
+    buf[6] =
+        (inst & 0x0FF00FFFu) | 0xE0000000 | (rx << 16u) | (ry << 12u);  // LDRxx Ry, [Rx],+/-Rm{, <shift>}
+    buf[7] = 0xE58D0008 | (ry << 12u);                                  // STR Ry, [SP, #8]
+    buf[8] = 0xE8BD8000 | (1u << rx) | (1u << ry);                      // POP {Rx, Ry, PC}
+    return 36;
+  } else {
+    buf[0] = 0x0A000000u | (cond << 28u);  // B<c> #0
+    buf[1] = 0xEA000005;                   // B #20
+    buf[2] = 0xE52D0004 | (rx << 12u);     // PUSH {Rx}
+    buf[3] = 0xE59F0000 | (rx << 12u);     // LDR Rx, [PC, #0]
+    buf[4] = 0xEA000000;                   // B #0
+    buf[5] = pc;
+    buf[6] = (inst & 0x0FF0FFFFu) | 0xE0000000 | (rx << 16u);  // LDRxx Rt, [Rx],+/-Rm{, <shift>}
+    buf[7] = 0xE49D0004 | (rx << 12u);                         // POP {Rx}
+    return 32;
+  }
+}
+
+size_t sh_a32_rewrite(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a32_rewrite_info_t *rinfo) {
+  sh_a32_type_t type = sh_a32_get_type(inst);
+  SH_LOG_INFO("a32 rewrite: type %d, inst %" PRIx32, type, inst);
+
+  // We will only overwrite 4 to 8 bytes on A32, so PC cannot be in the coverage.
+  // In this case, the add/sub/mov/ldr_reg instruction does not need to consider
+  // the problem of PC in the coverage area when rewriting.
+
+  if (type == B_A1 || type == BX_A1 || type == BL_IMM_A1 || type == BLX_IMM_A2)
+    return sh_a32_rewrite_b(buf, inst, pc, type, rinfo);
+  else if (type == ADD_REG_A1 || type == ADD_REG_PC_A1 || type == SUB_REG_A1 || type == SUB_REG_PC_A1)
+    return sh_a32_rewrite_add_or_sub(buf, inst, pc);
+  else if (type == ADR_A1 || type == ADR_A2)
+    return sh_a32_rewrite_adr(buf, inst, pc, type, rinfo);
+  else if (type == MOV_REG_A1 || type == MOV_REG_PC_A1)
+    return sh_a32_rewrite_mov(buf, inst, pc);
+  else if (type == LDR_LIT_A1 || type == LDR_LIT_PC_A1 || type == LDRB_LIT_A1 || type == LDRD_LIT_A1 ||
+           type == LDRH_LIT_A1 || type == LDRSB_LIT_A1 || type == LDRSH_LIT_A1)
+    return sh_a32_rewrite_ldr_lit(buf, inst, pc, type, rinfo);
+  else if (type == LDR_REG_A1 || type == LDR_REG_PC_A1 || type == LDRB_REG_A1 || type == LDRD_REG_A1 ||
+           type == LDRH_REG_A1 || type == LDRSB_REG_A1 || type == LDRSH_REG_A1)
+    return sh_a32_rewrite_ldr_reg(buf, inst, pc, type);
+  else {
+    // IGNORED
+    buf[0] = inst;
+    return 4;
+  }
+}
+
+size_t sh_a32_absolute_jump(uint32_t *buf, uintptr_t addr) {
+  buf[0] = 0xE51FF004;  // LDR PC, [PC, #-4]
+  buf[1] = addr;
+  return 8;
+}
+
+size_t sh_a32_relative_jump(uint32_t *buf, uintptr_t addr, uintptr_t pc) {
+  buf[0] = 0xEA000000 | (((addr - pc) & 0x03FFFFFFu) >> 2u);  // B <label>
+  return 4;
+}
diff --git a/app/src/main/cpp/shadowhook/arch/arm/sh_a32.h b/app/src/main/cpp/shadowhook/arch/arm/sh_a32.h
new file mode 100644
index 0000000000000000000000000000000000000000..efe159209e8085261801c3a2bfb00b3213df9fa8
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/arch/arm/sh_a32.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#pragma once
+#include <stddef.h>
+#include <stdint.h>
+
+typedef struct {
+  uintptr_t overwrite_start_addr;
+  uintptr_t overwrite_end_addr;
+  uint32_t *rewrite_buf;
+  size_t rewrite_buf_offset;
+  size_t rewrite_inst_lens[2];
+  size_t rewrite_inst_lens_cnt;
+} sh_a32_rewrite_info_t;
+
+size_t sh_a32_get_rewrite_inst_len(uint32_t inst);
+size_t sh_a32_rewrite(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a32_rewrite_info_t *rinfo);
+
+size_t sh_a32_absolute_jump(uint32_t *buf, uintptr_t addr);
+size_t sh_a32_relative_jump(uint32_t *buf, uintptr_t addr, uintptr_t pc);
diff --git a/app/src/main/cpp/shadowhook/arch/arm/sh_inst.c b/app/src/main/cpp/shadowhook/arch/arm/sh_inst.c
new file mode 100644
index 0000000000000000000000000000000000000000..9ca310fd22c8e18987fcbaecc79414456132a1a8
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/arch/arm/sh_inst.c
@@ -0,0 +1,523 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#include "sh_inst.h"
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+
+#include "sh_a32.h"
+#include "sh_config.h"
+#include "sh_enter.h"
+#include "sh_exit.h"
+#include "sh_log.h"
+#include "sh_sig.h"
+#include "sh_t16.h"
+#include "sh_t32.h"
+#include "sh_txx.h"
+#include "sh_util.h"
+#include "shadowhook.h"
+
+static void sh_inst_get_thumb_rewrite_info(sh_inst_t *self, uintptr_t target_addr,
+                                           sh_txx_rewrite_info_t *rinfo) {
+  memset(rinfo, 0, sizeof(sh_txx_rewrite_info_t));
+
+  size_t idx = 0;
+  uintptr_t target_addr_offset = 0;
+  uintptr_t pc = target_addr + 4;
+  size_t rewrite_len = 0;
+
+  while (rewrite_len < self->backup_len) {
+    // IT block
+    sh_t16_it_t it;
+    if (sh_t16_parse_it(&it, *((uint16_t *)(target_addr + target_addr_offset)), pc)) {
+      rewrite_len += (2 + it.insts_len);
+
+      size_t it_block_idx = idx++;
+      size_t it_block_len = 4 + 4;  // IT-else + IT-then
+      for (size_t i = 0, j = 0; i < it.insts_cnt; i++) {
+        bool is_thumb32 = sh_util_is_thumb32((uintptr_t)(&(it.insts[j])));
+        if (is_thumb32) {
+          it_block_len += sh_t32_get_rewrite_inst_len(it.insts[j], it.insts[j + 1]);
+          rinfo->inst_lens[idx++] = 0;
+          rinfo->inst_lens[idx++] = 0;
+          j += 2;
+        } else {
+          it_block_len += sh_t16_get_rewrite_inst_len(it.insts[j]);
+          rinfo->inst_lens[idx++] = 0;
+          j += 1;
+        }
+      }
+      rinfo->inst_lens[it_block_idx] = it_block_len;
+
+      target_addr_offset += (2 + it.insts_len);
+      pc += (2 + it.insts_len);
+    }
+    // not IT block
+    else {
+      bool is_thumb32 = sh_util_is_thumb32(target_addr + target_addr_offset);
+      size_t inst_len = (is_thumb32 ? 4 : 2);
+      rewrite_len += inst_len;
+
+      if (is_thumb32) {
+        rinfo->inst_lens[idx++] =
+            sh_t32_get_rewrite_inst_len(*((uint16_t *)(target_addr + target_addr_offset)),
+                                        *((uint16_t *)(target_addr + target_addr_offset + 2)));
+        rinfo->inst_lens[idx++] = 0;
+      } else
+        rinfo->inst_lens[idx++] =
+            sh_t16_get_rewrite_inst_len(*((uint16_t *)(target_addr + target_addr_offset)));
+
+      target_addr_offset += inst_len;
+      pc += inst_len;
+    }
+  }
+
+  rinfo->start_addr = target_addr;
+  rinfo->end_addr = target_addr + rewrite_len;
+  rinfo->buf = (uint16_t *)self->enter_addr;
+  rinfo->buf_offset = 0;
+  rinfo->inst_lens_cnt = idx;
+}
+
+static int sh_inst_hook_thumb_rewrite(sh_inst_t *self, uintptr_t target_addr, uintptr_t *orig_addr,
+                                      uintptr_t *orig_addr2, size_t *rewrite_len) {
+  // backup original instructions (length: 4 or 8 or 10)
+  memcpy((void *)(self->backup), (void *)target_addr, self->backup_len);
+
+  // package the information passed to rewrite
+  sh_txx_rewrite_info_t rinfo;
+  sh_inst_get_thumb_rewrite_info(self, target_addr, &rinfo);
+
+  // backup and rewrite original instructions
+  uintptr_t target_addr_offset = 0;
+  uintptr_t pc = target_addr + 4;
+  *rewrite_len = 0;
+  while (*rewrite_len < self->backup_len) {
+    // IT block
+    sh_t16_it_t it;
+    if (sh_t16_parse_it(&it, *((uint16_t *)(target_addr + target_addr_offset)), pc)) {
+      *rewrite_len += (2 + it.insts_len);
+
+      // save space holder point of IT-else B instruction
+      uintptr_t enter_inst_else_p = self->enter_addr + rinfo.buf_offset;
+      rinfo.buf_offset += 2;  // B<c> <label>
+      rinfo.buf_offset += 2;  // NOP
+
+      // rewrite IT block
+      size_t enter_inst_else_len = 4;  // B<c> + NOP + B + NOP
+      size_t enter_inst_then_len = 0;  // B + NOP
+      uintptr_t enter_inst_then_p = 0;
+      for (size_t i = 0, j = 0; i < it.insts_cnt; i++) {
+        if (i == it.insts_else_cnt) {
+          // save space holder point of IT-then (for B instruction)
+          enter_inst_then_p = self->enter_addr + rinfo.buf_offset;
+          rinfo.buf_offset += 2;  // B <label>
+          rinfo.buf_offset += 2;  // NOP
+
+          // fill IT-else B instruction
+          sh_t16_rewrite_it_else((uint16_t *)enter_inst_else_p, (uint16_t)enter_inst_else_len, &it);
+        }
+
+        // rewrite instructions in IT block
+        bool is_thumb32 = sh_util_is_thumb32((uintptr_t)(&(it.insts[j])));
+        size_t len;
+        if (is_thumb32)
+          len = sh_t32_rewrite((uint16_t *)(self->enter_addr + rinfo.buf_offset), it.insts[j],
+                               it.insts[j + 1], it.pcs[i], &rinfo);
+        else
+          len = sh_t16_rewrite((uint16_t *)(self->enter_addr + rinfo.buf_offset), it.insts[j], it.pcs[i],
+                               &rinfo);
+        if (0 == len) return SHADOWHOOK_ERRNO_HOOK_REWRITE_FAILED;
+        rinfo.buf_offset += len;
+        j += (is_thumb32 ? 2 : 1);
+
+        // save the total offset for ELSE/THEN in enter
+        if (i < it.insts_else_cnt)
+          enter_inst_else_len += len;
+        else
+          enter_inst_then_len += len;
+
+        if (i == it.insts_cnt - 1) {
+          // fill IT-then B instruction
+          sh_t16_rewrite_it_then((uint16_t *)enter_inst_then_p, (uint16_t)enter_inst_then_len);
+        }
+      }
+
+      target_addr_offset += (2 + it.insts_len);
+      pc += (2 + it.insts_len);
+    }
+    // not IT block
+    else {
+      bool is_thumb32 = sh_util_is_thumb32(target_addr + target_addr_offset);
+      size_t inst_len = (is_thumb32 ? 4 : 2);
+      *rewrite_len += inst_len;
+
+      // rewrite original instructions (fill in enter)
+      SH_LOG_INFO("thumb rewrite: offset %zu, pc %" PRIxPTR, rinfo.buf_offset, pc);
+      size_t len;
+      if (is_thumb32)
+        len = sh_t32_rewrite((uint16_t *)(self->enter_addr + rinfo.buf_offset),
+                             *((uint16_t *)(target_addr + target_addr_offset)),
+                             *((uint16_t *)(target_addr + target_addr_offset + 2)), pc, &rinfo);
+      else
+        len = sh_t16_rewrite((uint16_t *)(self->enter_addr + rinfo.buf_offset),
+                             *((uint16_t *)(target_addr + target_addr_offset)), pc, &rinfo);
+      if (0 == len) return SHADOWHOOK_ERRNO_HOOK_REWRITE_FAILED;
+      rinfo.buf_offset += len;
+
+      target_addr_offset += inst_len;
+      pc += inst_len;
+    }
+  }
+  SH_LOG_INFO("thumb rewrite: len %zu to %zu", *rewrite_len, rinfo.buf_offset);
+
+  // absolute jump back to remaining original instructions (fill in enter)
+  rinfo.buf_offset += sh_t32_absolute_jump((uint16_t *)(self->enter_addr + rinfo.buf_offset), true,
+                                           SH_UTIL_SET_BIT0(target_addr + *rewrite_len));
+  sh_util_clear_cache(self->enter_addr, rinfo.buf_offset);
+
+  // save original function address
+  if (NULL != orig_addr) __atomic_store_n(orig_addr, SH_UTIL_SET_BIT0(self->enter_addr), __ATOMIC_SEQ_CST);
+  if (NULL != orig_addr2) __atomic_store_n(orig_addr2, SH_UTIL_SET_BIT0(self->enter_addr), __ATOMIC_SEQ_CST);
+  return 0;
+}
+
+#ifdef SH_CONFIG_DETECT_THUMB_TAIL_ALIGNED
+static bool sh_inst_thumb_is_long_enough(uintptr_t target_addr, size_t overwrite_len, xdl_info_t *dlinfo) {
+  if (overwrite_len <= dlinfo->dli_ssize) return true;
+
+  // check align-4 in the end of symbol
+  if ((overwrite_len == dlinfo->dli_ssize + 2) && ((target_addr + dlinfo->dli_ssize) % 4 == 2)) {
+    uintptr_t sym_end = target_addr + dlinfo->dli_ssize;
+    if (0 != sh_util_mprotect(sym_end, 2, PROT_READ | PROT_WRITE | PROT_EXEC)) return false;
+
+    // should be zero-ed
+    if (0 != *((uint16_t *)sym_end)) return false;
+
+    // should not belong to any symbol
+    void *dlcache = NULL;
+    xdl_info_t dlinfo2;
+    if (sh_util_get_api_level() >= __ANDROID_API_L__) {
+      xdl_addr((void *)SH_UTIL_SET_BIT0(sym_end), &dlinfo2, &dlcache);
+    } else {
+      SH_SIG_TRY(SIGSEGV, SIGBUS) {
+        xdl_addr((void *)SH_UTIL_SET_BIT0(sym_end), &dlinfo2, &dlcache);
+      }
+      SH_SIG_CATCH() {
+        memset(&dlinfo2, 0, sizeof(dlinfo2));
+        SH_LOG_WARN("thumb detect tail aligned: crashed");
+      }
+      SH_SIG_EXIT
+    }
+    xdl_addr_clean(&dlcache);
+    if (NULL != dlinfo2.dli_sname) return false;
+
+    // trust here is useless alignment data
+    return true;
+  }
+
+  return false;
+}
+#endif
+
+#ifdef SH_CONFIG_TRY_WITH_EXIT
+
+// B T4: [-16M, +16M - 2]
+#define SH_INST_T32_B_RANGE_LOW  (16777216)
+#define SH_INST_T32_B_RANGE_HIGH (16777214)
+
+static int sh_inst_hook_thumb_with_exit(sh_inst_t *self, uintptr_t target_addr, xdl_info_t *dlinfo,
+                                        uintptr_t new_addr, uintptr_t *orig_addr, uintptr_t *orig_addr2) {
+  int r;
+  target_addr = SH_UTIL_CLEAR_BIT0(target_addr);
+  uintptr_t pc = target_addr + 4;
+  self->backup_len = 4;
+
+#ifdef SH_CONFIG_DETECT_THUMB_TAIL_ALIGNED
+  if (!sh_inst_thumb_is_long_enough(target_addr, self->backup_len, dlinfo))
+    return SHADOWHOOK_ERRNO_HOOK_SYMSZ;
+#else
+  if (dlinfo->dli_ssize < self->backup_len) return SHADOWHOOK_ERRNO_HOOK_SYMSZ;
+#endif
+
+  // alloc an exit for absolute jump
+  sh_t32_absolute_jump((uint16_t *)self->exit, true, new_addr);
+  if (0 != (r = sh_exit_alloc(&self->exit_addr, &self->exit_type, pc, dlinfo, (uint8_t *)(self->exit),
+                              sizeof(self->exit), SH_INST_T32_B_RANGE_LOW, SH_INST_T32_B_RANGE_HIGH)))
+    return r;
+
+  // rewrite
+  if (0 != sh_util_mprotect(target_addr, dlinfo->dli_ssize, PROT_READ | PROT_WRITE | PROT_EXEC)) {
+    r = SHADOWHOOK_ERRNO_MPROT;
+    goto err;
+  }
+  size_t rewrite_len = 0;
+  SH_SIG_TRY(SIGSEGV, SIGBUS) {
+    r = sh_inst_hook_thumb_rewrite(self, target_addr, orig_addr, orig_addr2, &rewrite_len);
+  }
+  SH_SIG_CATCH() {
+    r = SHADOWHOOK_ERRNO_HOOK_REWRITE_CRASH;
+    goto err;
+  }
+  SH_SIG_EXIT
+  if (0 != r) goto err;
+
+  // relative jump to the exit by overwriting the head of original function
+  sh_t32_relative_jump((uint16_t *)self->trampo, self->exit_addr, pc);
+  __atomic_thread_fence(__ATOMIC_SEQ_CST);
+  if (0 != (r = sh_util_write_inst(target_addr, self->trampo, self->backup_len))) goto err;
+
+  SH_LOG_INFO("thumb: hook (WITH EXIT) OK. target %" PRIxPTR " -> exit %" PRIxPTR " -> new %" PRIxPTR
+              " -> enter %" PRIxPTR " -> remaining %" PRIxPTR,
+              target_addr, self->exit_addr, new_addr, self->enter_addr,
+              SH_UTIL_SET_BIT0(target_addr + rewrite_len));
+  return 0;
+
+err:
+  sh_exit_free(self->exit_addr, self->exit_type, (uint8_t *)(self->exit), sizeof(self->exit));
+  self->exit_addr = 0;  // this is a flag for with-exit or without-exit
+  return r;
+}
+#endif
+
+static int sh_inst_hook_thumb_without_exit(sh_inst_t *self, uintptr_t target_addr, xdl_info_t *dlinfo,
+                                           uintptr_t new_addr, uintptr_t *orig_addr, uintptr_t *orig_addr2) {
+  int r;
+  target_addr = SH_UTIL_CLEAR_BIT0(target_addr);
+  bool is_align4 = (0 == (target_addr % 4));
+  self->backup_len = (is_align4 ? 8 : 10);
+
+#ifdef SH_CONFIG_DETECT_THUMB_TAIL_ALIGNED
+  if (!sh_inst_thumb_is_long_enough(target_addr, self->backup_len, dlinfo))
+    return SHADOWHOOK_ERRNO_HOOK_SYMSZ;
+#else
+  if (dlinfo->dli_ssize < self->backup_len) return SHADOWHOOK_ERRNO_HOOK_SYMSZ;
+#endif
+
+  // rewrite
+  if (0 != sh_util_mprotect(target_addr, dlinfo->dli_ssize, PROT_READ | PROT_WRITE | PROT_EXEC))
+    return SHADOWHOOK_ERRNO_MPROT;
+  size_t rewrite_len = 0;
+  SH_SIG_TRY(SIGSEGV, SIGBUS) {
+    r = sh_inst_hook_thumb_rewrite(self, target_addr, orig_addr, orig_addr2, &rewrite_len);
+  }
+  SH_SIG_CATCH() {
+    return SHADOWHOOK_ERRNO_HOOK_REWRITE_CRASH;
+  }
+  SH_SIG_EXIT
+  if (0 != r) return r;
+
+  // absolute jump to new function address by overwriting the head of original function
+  sh_t32_absolute_jump((uint16_t *)self->trampo, is_align4, new_addr);
+  __atomic_thread_fence(__ATOMIC_SEQ_CST);
+  if (0 != (r = sh_util_write_inst(target_addr, self->trampo, self->backup_len))) return r;
+
+  SH_LOG_INFO("thumb: hook (WITHOUT EXIT) OK. target %" PRIxPTR " -> new %" PRIxPTR " -> enter %" PRIxPTR
+              " -> remaining %" PRIxPTR,
+              target_addr, new_addr, self->enter_addr, SH_UTIL_SET_BIT0(target_addr + rewrite_len));
+  return 0;
+}
+
+static int sh_inst_hook_arm_rewrite(sh_inst_t *self, uintptr_t target_addr, uintptr_t *orig_addr,
+                                    uintptr_t *orig_addr2) {
+  // backup original instructions (length: 4 or 8)
+  memcpy((void *)(self->backup), (void *)target_addr, self->backup_len);
+
+  // package the information passed to rewrite
+  sh_a32_rewrite_info_t rinfo;
+  rinfo.overwrite_start_addr = target_addr;
+  rinfo.overwrite_end_addr = target_addr + self->backup_len;
+  rinfo.rewrite_buf = (uint32_t *)self->enter_addr;
+  rinfo.rewrite_buf_offset = 0;
+  rinfo.rewrite_inst_lens_cnt = self->backup_len / 4;
+  for (uintptr_t i = 0; i < self->backup_len; i += 4)
+    rinfo.rewrite_inst_lens[i / 4] = sh_a32_get_rewrite_inst_len(*((uint32_t *)(target_addr + i)));
+
+  // rewrite original instructions (fill in enter)
+  uintptr_t pc = target_addr + 8;
+  for (uintptr_t i = 0; i < self->backup_len; i += 4, pc += 4) {
+    size_t offset = sh_a32_rewrite((uint32_t *)(self->enter_addr + rinfo.rewrite_buf_offset),
+                                   *((uint32_t *)(target_addr + i)), pc, &rinfo);
+    if (0 == offset) return SHADOWHOOK_ERRNO_HOOK_REWRITE_FAILED;
+    rinfo.rewrite_buf_offset += offset;
+  }
+
+  // absolute jump back to remaining original instructions (fill in enter)
+  rinfo.rewrite_buf_offset += sh_a32_absolute_jump((uint32_t *)(self->enter_addr + rinfo.rewrite_buf_offset),
+                                                   target_addr + self->backup_len);
+  sh_util_clear_cache(self->enter_addr, rinfo.rewrite_buf_offset);
+
+  // save original function address
+  if (NULL != orig_addr) __atomic_store_n(orig_addr, self->enter_addr, __ATOMIC_SEQ_CST);
+  if (NULL != orig_addr2) __atomic_store_n(orig_addr2, self->enter_addr, __ATOMIC_SEQ_CST);
+  return 0;
+}
+
+#ifdef SH_CONFIG_TRY_WITH_EXIT
+
+// B A1: [-32M, +32M - 4]
+#define SH_INST_A32_B_RANGE_LOW  (33554432)
+#define SH_INST_A32_B_RANGE_HIGH (33554428)
+
+static int sh_inst_hook_arm_with_exit(sh_inst_t *self, uintptr_t target_addr, xdl_info_t *dlinfo,
+                                      uintptr_t new_addr, uintptr_t *orig_addr, uintptr_t *orig_addr2) {
+  int r;
+  uintptr_t pc = target_addr + 8;
+  self->backup_len = 4;
+
+  if (dlinfo->dli_ssize < self->backup_len) return SHADOWHOOK_ERRNO_HOOK_SYMSZ;
+
+  // alloc an exit for absolute jump
+  sh_a32_absolute_jump(self->exit, new_addr);
+  if (0 != (r = sh_exit_alloc(&self->exit_addr, &self->exit_type, pc, dlinfo, (uint8_t *)(self->exit),
+                              sizeof(self->exit), SH_INST_A32_B_RANGE_LOW, SH_INST_A32_B_RANGE_HIGH)))
+    return r;
+
+  // rewrite
+  if (0 != sh_util_mprotect(target_addr, self->backup_len, PROT_READ | PROT_WRITE | PROT_EXEC)) {
+    r = SHADOWHOOK_ERRNO_MPROT;
+    goto err;
+  }
+  SH_SIG_TRY(SIGSEGV, SIGBUS) {
+    r = sh_inst_hook_arm_rewrite(self, target_addr, orig_addr, orig_addr2);
+  }
+  SH_SIG_CATCH() {
+    r = SHADOWHOOK_ERRNO_HOOK_REWRITE_CRASH;
+    goto err;
+  }
+  SH_SIG_EXIT
+  if (0 != r) goto err;
+
+  // relative jump to the exit by overwriting the head of original function
+  sh_a32_relative_jump(self->trampo, self->exit_addr, pc);
+  __atomic_thread_fence(__ATOMIC_SEQ_CST);
+  if (0 != (r = sh_util_write_inst(target_addr, self->trampo, self->backup_len))) goto err;
+
+  SH_LOG_INFO("a32: hook (WITH EXIT) OK. target %" PRIxPTR " -> exit %" PRIxPTR " -> new %" PRIxPTR
+              " -> enter %" PRIxPTR " -> remaining %" PRIxPTR,
+              target_addr, self->exit_addr, new_addr, self->enter_addr, target_addr + self->backup_len);
+  return 0;
+
+err:
+  sh_exit_free(self->exit_addr, self->exit_type, (uint8_t *)(self->exit), sizeof(self->exit));
+  self->exit_addr = 0;  // this is a flag for with-exit or without-exit
+  return r;
+}
+
+#endif
+
+static int sh_inst_hook_arm_without_exit(sh_inst_t *self, uintptr_t target_addr, xdl_info_t *dlinfo,
+                                         uintptr_t new_addr, uintptr_t *orig_addr, uintptr_t *orig_addr2) {
+  int r;
+  self->backup_len = 8;
+
+  if (dlinfo->dli_ssize < self->backup_len) return SHADOWHOOK_ERRNO_HOOK_SYMSZ;
+
+  // rewrite
+  if (0 != sh_util_mprotect(target_addr, self->backup_len, PROT_READ | PROT_WRITE | PROT_EXEC))
+    return SHADOWHOOK_ERRNO_MPROT;
+  SH_SIG_TRY(SIGSEGV, SIGBUS) {
+    r = sh_inst_hook_arm_rewrite(self, target_addr, orig_addr, orig_addr2);
+  }
+  SH_SIG_CATCH() {
+    return SHADOWHOOK_ERRNO_HOOK_REWRITE_CRASH;
+  }
+  SH_SIG_EXIT
+  if (0 != r) return r;
+
+  // absolute jump to new function address by overwriting the head of original function
+  sh_a32_absolute_jump(self->trampo, new_addr);
+  __atomic_thread_fence(__ATOMIC_SEQ_CST);
+  if (0 != (r = sh_util_write_inst(target_addr, self->trampo, self->backup_len))) return r;
+
+  SH_LOG_INFO("a32: hook (WITHOUT EXIT) OK. target %" PRIxPTR " -> new %" PRIxPTR " -> enter %" PRIxPTR
+              " -> remaining %" PRIxPTR,
+              target_addr, new_addr, self->enter_addr, target_addr + self->backup_len);
+  return 0;
+}
+
+int sh_inst_hook(sh_inst_t *self, uintptr_t target_addr, xdl_info_t *dlinfo, uintptr_t new_addr,
+                 uintptr_t *orig_addr, uintptr_t *orig_addr2) {
+  self->enter_addr = sh_enter_alloc();
+  if (0 == self->enter_addr) return SHADOWHOOK_ERRNO_HOOK_ENTER;
+
+  int r;
+  if (SH_UTIL_IS_THUMB(target_addr)) {
+#ifdef SH_CONFIG_TRY_WITH_EXIT
+    if (0 == (r = sh_inst_hook_thumb_with_exit(self, target_addr, dlinfo, new_addr, orig_addr, orig_addr2)))
+      return r;
+#endif
+    if (0 ==
+        (r = sh_inst_hook_thumb_without_exit(self, target_addr, dlinfo, new_addr, orig_addr, orig_addr2)))
+      return r;
+  } else {
+#ifdef SH_CONFIG_TRY_WITH_EXIT
+    if (0 == (r = sh_inst_hook_arm_with_exit(self, target_addr, dlinfo, new_addr, orig_addr, orig_addr2)))
+      return r;
+#endif
+    if (0 == (r = sh_inst_hook_arm_without_exit(self, target_addr, dlinfo, new_addr, orig_addr, orig_addr2)))
+      return r;
+  }
+
+  // hook failed
+  if (NULL != orig_addr) *orig_addr = 0;
+  if (NULL != orig_addr2) *orig_addr2 = 0;
+  sh_enter_free(self->enter_addr);
+  return r;
+}
+
+int sh_inst_unhook(sh_inst_t *self, uintptr_t target_addr) {
+  int r;
+  bool is_thumb = SH_UTIL_IS_THUMB(target_addr);
+
+  if (is_thumb) target_addr = SH_UTIL_CLEAR_BIT0(target_addr);
+
+  // restore the instructions at the target address
+  SH_SIG_TRY(SIGSEGV, SIGBUS) {
+    r = memcmp((void *)target_addr, self->trampo, self->backup_len);
+  }
+  SH_SIG_CATCH() {
+    return SHADOWHOOK_ERRNO_UNHOOK_CMP_CRASH;
+  }
+  SH_SIG_EXIT
+  if (0 != r) return SHADOWHOOK_ERRNO_UNHOOK_TRAMPO_MISMATCH;
+  if (0 != (r = sh_util_write_inst(target_addr, self->backup, self->backup_len))) return r;
+  __atomic_thread_fence(__ATOMIC_SEQ_CST);
+
+  // free memory space for exit
+  if (0 != self->exit_addr)
+    if (0 !=
+        (r = sh_exit_free(self->exit_addr, self->exit_type, (uint8_t *)(self->exit), sizeof(self->exit))))
+      return r;
+
+  // free memory space for enter
+  sh_enter_free(self->enter_addr);
+
+  SH_LOG_INFO("%s: unhook OK. target %" PRIxPTR, is_thumb ? "thumb" : "a32", target_addr);
+  return 0;
+}
diff --git a/app/src/main/cpp/shadowhook/arch/arm/sh_inst.h b/app/src/main/cpp/shadowhook/arch/arm/sh_inst.h
new file mode 100644
index 0000000000000000000000000000000000000000..ed38ab740a274a7b7ab614ada25a3e697d5f3550
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/arch/arm/sh_inst.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#pragma once
+#include <stdint.h>
+
+#include "xdl.h"
+
+typedef struct {
+  uint32_t trampo[4];   // align 16 // length == backup_len
+  uint8_t backup[16];   // align 16
+  uint16_t backup_len;  // == 4 or 8 or 10
+  uint16_t exit_type;
+  uintptr_t exit_addr;
+  uint32_t exit[2];
+  uintptr_t enter_addr;
+} sh_inst_t;
+
+int sh_inst_hook(sh_inst_t *self, uintptr_t target_addr, xdl_info_t *dlinfo, uintptr_t new_addr,
+                 uintptr_t *orig_addr, uintptr_t *orig_addr2);
+int sh_inst_unhook(sh_inst_t *self, uintptr_t target_addr);
diff --git a/app/src/main/cpp/shadowhook/arch/arm/sh_t16.c b/app/src/main/cpp/shadowhook/arch/arm/sh_t16.c
new file mode 100644
index 0000000000000000000000000000000000000000..d2505264548284351a82d8419d5ffda664309ab5
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/arch/arm/sh_t16.c
@@ -0,0 +1,284 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Pengying Xu (xupengying@bytedance.com) on 2021-04-11.
+
+#include "sh_t16.h"
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "sh_log.h"
+#include "sh_util.h"
+
+// https://developer.arm.com/documentation/ddi0406/latest
+// https://developer.arm.com/documentation/ddi0597/latest
+
+typedef enum {
+  IGNORED = 0,
+  IT_T1,
+  B_T1,
+  B_T2,
+  BX_T1,
+  ADD_REG_T2,
+  MOV_REG_T1,
+  ADR_T1,
+  LDR_LIT_T1,
+  CBZ_T1,
+  CBNZ_T1
+} sh_t16_type_t;
+
+static sh_t16_type_t sh_t16_get_type(uint16_t inst) {
+  if (((inst & 0xFF00u) == 0xBF00) && ((inst & 0x000Fu) != 0x0000) && ((inst & 0x00F0u) != 0x00F0))
+    return IT_T1;
+  else if (((inst & 0xF000u) == 0xD000) && ((inst & 0x0F00u) != 0x0F00) && ((inst & 0x0F00u) != 0x0E00))
+    return B_T1;
+  else if ((inst & 0xF800u) == 0xE000)
+    return B_T2;
+  else if ((inst & 0xFFF8u) == 0x4778)
+    return BX_T1;
+  else if (((inst & 0xFF78u) == 0x4478) && ((inst & 0x0087u) != 0x0085))
+    return ADD_REG_T2;
+  else if ((inst & 0xFF78u) == 0x4678)
+    return MOV_REG_T1;
+  else if ((inst & 0xF800u) == 0xA000)
+    return ADR_T1;
+  else if ((inst & 0xF800u) == 0x4800)
+    return LDR_LIT_T1;
+  else if ((inst & 0xFD00u) == 0xB100)
+    return CBZ_T1;
+  else if ((inst & 0xFD00u) == 0xB900)
+    return CBNZ_T1;
+  else
+    return IGNORED;
+}
+
+size_t sh_t16_get_rewrite_inst_len(uint16_t inst) {
+  static uint8_t map[] = {
+      4,   // IGNORED
+      0,   // IT_T1
+      12,  // B_T1
+      8,   // B_T2
+      8,   // BX_T1
+      16,  // ADD_REG_T2
+      12,  // MOV_REG_T1
+      8,   // ADR_T1
+      12,  // LDR_LIT_T1
+      12,  // CBZ_T1
+      12   // CBNZ_T1
+  };
+
+  return (size_t)(map[sh_t16_get_type(inst)]);
+}
+
+static size_t sh_t16_rewrite_b(uint16_t *buf, uint16_t inst, uintptr_t pc, sh_t16_type_t type,
+                               sh_txx_rewrite_info_t *rinfo) {
+  uint32_t addr;
+  if (type == B_T1) {
+    uint32_t imm8 = SH_UTIL_GET_BITS_16(inst, 7, 0);
+    addr = pc + SH_UTIL_SIGN_EXTEND_32(imm8 << 1u, 9u);
+    addr = SH_UTIL_SET_BIT0(addr);  // thumb -> thumb
+  } else if (type == B_T2) {
+    uint32_t imm11 = SH_UTIL_GET_BITS_16(inst, 10, 0);
+    addr = pc + SH_UTIL_SIGN_EXTEND_32(imm11 << 1u, 12u);
+    addr = SH_UTIL_SET_BIT0(addr);  // thumb -> thumb
+  } else {
+    // type == BX_T1
+    // BX PC
+    // PC must be even, and the "BX PC" instruction must be at a 4-byte aligned address,
+    // so the instruction set must be exchanged from "thumb" to "arm".
+    addr = pc;  // thumb -> arm
+  }
+  addr = sh_txx_fix_addr(addr, rinfo);
+
+  size_t idx = 0;
+  if (type == B_T1) {
+    buf[idx++] = inst & 0xFF00u;  // B<c> #0
+    buf[idx++] = 0xE003;          // B PC, #6
+  }
+  buf[idx++] = 0xF8DF;  // LDR.W PC, [PC]
+  buf[idx++] = 0xF000;  // ...
+  buf[idx++] = addr & 0xFFFFu;
+  buf[idx++] = addr >> 16u;
+  return idx * 2;  // 8 or 12
+}
+
+static size_t sh_t16_rewrite_add(uint16_t *buf, uint16_t inst, uintptr_t pc) {
+  // ADD<c> <Rdn>, PC
+  uint16_t dn = SH_UTIL_GET_BIT_16(inst, 7);
+  uint16_t rdn = SH_UTIL_GET_BITS_16(inst, 2, 0);
+  uint16_t rd = (uint16_t)(dn << 3u) | rdn;
+  uint16_t rx = (rd == 0) ? 1 : 0;  // r0 - r1
+
+  buf[0] = (uint16_t)(0xB400u | (1u << rx));         // PUSH {Rx}
+  buf[1] = 0x4802u | (uint16_t)(rx << 8u);           // LDR Rx, [PC, #8]
+  buf[2] = (inst & 0xFF87u) | (uint16_t)(rx << 3u);  // ADD Rd, Rx
+  buf[3] = (uint16_t)(0xBC00u | (1u << rx));         // POP {Rx}
+  buf[4] = 0xE002;                                   // B #4
+  buf[5] = 0xBF00;
+  buf[6] = pc & 0xFFFFu;
+  buf[7] = pc >> 16u;
+  return 16;
+}
+
+static size_t sh_t16_rewrite_mov(uint16_t *buf, uint16_t inst, uintptr_t pc) {
+  // MOV<c> <Rd>, PC
+  uint16_t D = SH_UTIL_GET_BIT_16(inst, 7);
+  uint16_t rd = SH_UTIL_GET_BITS_16(inst, 2, 0);
+  uint16_t d = (uint16_t)(D << 3u) | rd;  // r0 - r15
+
+  buf[0] = 0xF8DF;                     // LDR.W Rd, [PC, #4]
+  buf[1] = (uint16_t)(d << 12u) + 4u;  // ...
+  buf[2] = 0xE002;                     // B #4
+  buf[3] = 0xBF00;                     // NOP
+  buf[4] = pc & 0xFFFFu;
+  buf[5] = pc >> 16u;
+  return 12;
+}
+
+static size_t sh_t16_rewrite_adr(uint16_t *buf, uint16_t inst, uintptr_t pc, sh_txx_rewrite_info_t *rinfo) {
+  // ADR<c> <Rd>, <label>
+  uint16_t rd = SH_UTIL_GET_BITS_16(inst, 10, 8);  // r0 - r7
+  uint16_t imm8 = SH_UTIL_GET_BITS_16(inst, 7, 0);
+  uint32_t addr = SH_UTIL_ALIGN_4(pc) + (uint32_t)(imm8 << 2u);
+  if (sh_txx_is_addr_need_fix(addr, rinfo)) return 0;  // rewrite failed
+
+  buf[0] = 0x4800u | (uint16_t)(rd << 8u);  // LDR Rd, [PC]
+  buf[1] = 0xE001;                          // B #2
+  buf[2] = addr & 0xFFFFu;
+  buf[3] = addr >> 16u;
+  return 8;
+}
+
+static size_t sh_t16_rewrite_ldr(uint16_t *buf, uint16_t inst, uintptr_t pc, sh_txx_rewrite_info_t *rinfo) {
+  // LDR<c> <Rt>, <label>
+  uint16_t rt = SH_UTIL_GET_BITS_16(inst, 10, 8);  // r0 - r7
+  uint16_t imm8 = SH_UTIL_GET_BITS_16(inst, 7, 0);
+  uint32_t addr = SH_UTIL_ALIGN_4(pc) + (uint32_t)(imm8 << 2u);
+  if (sh_txx_is_addr_need_fix(addr, rinfo)) return 0;  // rewrite failed
+
+  buf[0] = 0x4800u | (uint16_t)(rt << 8u);  // LDR Rt, [PC]
+  buf[1] = 0xE001;                          // B #2
+  buf[2] = addr & 0xFFFFu;
+  buf[3] = addr >> 16u;
+  buf[4] = 0x6800u | (uint16_t)(rt << 3u) | rt;  // LDR Rt, [Rt]
+  buf[5] = 0xBF00;                               // NOP
+  return 12;
+}
+
+static size_t sh_t16_rewrite_cb(uint16_t *buf, uint16_t inst, uintptr_t pc, sh_txx_rewrite_info_t *rinfo) {
+  // CB{N}Z <Rn>, <label>
+  uint16_t i = SH_UTIL_GET_BIT_16(inst, 9);
+  uint16_t imm5 = SH_UTIL_GET_BITS_16(inst, 7, 3);
+  uint32_t imm32 = (uint32_t)(i << 6u) | (uint32_t)(imm5 << 1u);
+  uint32_t addr = SH_UTIL_SET_BIT0(pc + imm32);  // thumb -> thumb
+  addr = sh_txx_fix_addr(addr, rinfo);
+
+  buf[0] = inst & 0xFD07u;  // CB(N)Z Rn, #0
+  buf[1] = 0xE003;          // B PC, #6
+  buf[2] = 0xF8DF;          // LDR.W PC, [PC]
+  buf[3] = 0xF000;          // ...
+  buf[4] = addr & 0xFFFFu;
+  buf[5] = addr >> 16u;
+  return 12;
+}
+
+size_t sh_t16_rewrite(uint16_t *buf, uint16_t inst, uintptr_t pc, sh_txx_rewrite_info_t *rinfo) {
+  sh_t16_type_t type = sh_t16_get_type(inst);
+  SH_LOG_INFO("t16 rewrite: type %d, inst %" PRIx16, type, inst);
+
+  if (type == B_T1 || type == B_T2 || type == BX_T1)
+    return sh_t16_rewrite_b(buf, inst, pc, type, rinfo);
+  else if (type == ADD_REG_T2)
+    return sh_t16_rewrite_add(buf, inst, pc);
+  else if (type == MOV_REG_T1)
+    return sh_t16_rewrite_mov(buf, inst, pc);
+  else if (type == ADR_T1)
+    return sh_t16_rewrite_adr(buf, inst, pc, rinfo);
+  else if (type == LDR_LIT_T1)
+    return sh_t16_rewrite_ldr(buf, inst, pc, rinfo);
+  else if (type == CBZ_T1 || type == CBNZ_T1)
+    return sh_t16_rewrite_cb(buf, inst, pc, rinfo);
+  else {
+    // IGNORED
+    buf[0] = inst;
+    buf[1] = 0xBF00;  // NOP
+    return 4;
+  }
+}
+
+static size_t sh_t16_get_it_insts_count(uint16_t inst) {
+  if ((inst & 0x1u) != 0) return 4;
+  if ((inst & 0x2u) != 0) return 3;
+  if ((inst & 0x4u) != 0) return 2;
+  return 1;
+}
+
+bool sh_t16_parse_it(sh_t16_it_t *it, uint16_t inst, uintptr_t pc) {
+  if (IT_T1 != sh_t16_get_type(inst)) return false;
+  SH_LOG_INFO("t16 rewrite: type IT, inst %" PRIx16, inst);
+
+  // address of the first inst in the IT block, skip the IT inst itself (2 bytes)
+  uintptr_t target_addr = pc - 4 + 2;
+
+  it->firstcond = (uint8_t)(inst >> 4u);
+  uint8_t firstcond_0 = it->firstcond & 1u;
+
+  memset(it, 0, sizeof(sh_t16_it_t));
+  it->insts_cnt = sh_t16_get_it_insts_count(inst);
+
+  size_t insts_idx = 0, pcs_idx = 0;
+  for (int parse_else = 1; parse_else >= 0; parse_else--)  // round 0: parse ELSE, round 1: THEN
+  {
+    uintptr_t target_offset = 0;
+    for (size_t i = 0; i < it->insts_cnt; i++) {
+      bool is_thumb32 = sh_util_is_thumb32(target_addr + target_offset);
+      uint8_t mask_x = (uint8_t)(inst >> (uint16_t)(4 - i)) & 1u;
+
+      if ((parse_else && mask_x != firstcond_0) ||  // parse ELSE or
+          (!parse_else && mask_x == firstcond_0))   // parse THEN
+      {
+        it->insts[insts_idx++] = *((uint16_t *)(target_addr + target_offset));
+        if (is_thumb32) it->insts[insts_idx++] = *((uint16_t *)(target_addr + target_offset + 2));
+
+        it->pcs[pcs_idx++] = target_addr + target_offset + 4;
+        if (parse_else) it->insts_else_cnt++;
+      }
+
+      target_offset += (is_thumb32 ? 4 : 2);
+    }
+  }
+  it->insts_len = insts_idx * 2;
+
+  return true;
+}
+
+void sh_t16_rewrite_it_else(uint16_t *buf, uint16_t imm9, sh_t16_it_t *it) {
+  buf[0] = 0xD000u | (uint16_t)(it->firstcond << 8u) | (uint16_t)(imm9 >> 1u);  // B<c> <label>
+  buf[1] = 0xBF00;                                                              // NOP
+}
+
+void sh_t16_rewrite_it_then(uint16_t *buf, uint16_t imm12) {
+  buf[0] = 0xE000u | (uint16_t)(imm12 >> 1u);  // B <label>
+  buf[1] = 0xBF00;                             // NOP
+}
diff --git a/app/src/main/cpp/shadowhook/arch/arm/sh_t16.h b/app/src/main/cpp/shadowhook/arch/arm/sh_t16.h
new file mode 100644
index 0000000000000000000000000000000000000000..9d68d79608ebf329d87f5c37cb5f23e9156c1d07
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/arch/arm/sh_t16.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Pengying Xu (xupengying@bytedance.com) on 2021-04-11.
+
+#pragma once
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "sh_txx.h"
+
+typedef struct {
+  uint16_t insts[8];
+  size_t insts_len;       // 2 - 16 (bytes)
+  size_t insts_cnt;       // 1 - 4
+  size_t insts_else_cnt;  // 0 - 3
+  uintptr_t pcs[4];
+  uint8_t firstcond;
+  uint8_t padding[3];
+} sh_t16_it_t;
+
+bool sh_t16_parse_it(sh_t16_it_t *it, uint16_t inst, uintptr_t pc);
+void sh_t16_rewrite_it_else(uint16_t *buf, uint16_t imm9, sh_t16_it_t *it);
+void sh_t16_rewrite_it_then(uint16_t *buf, uint16_t imm12);
+
+size_t sh_t16_get_rewrite_inst_len(uint16_t inst);
+size_t sh_t16_rewrite(uint16_t *buf, uint16_t inst, uintptr_t pc, sh_txx_rewrite_info_t *rinfo);
diff --git a/app/src/main/cpp/shadowhook/arch/arm/sh_t32.c b/app/src/main/cpp/shadowhook/arch/arm/sh_t32.c
new file mode 100644
index 0000000000000000000000000000000000000000..bb9e859970bde9f4d925c1459619835fccedd1bd
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/arch/arm/sh_t32.c
@@ -0,0 +1,408 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#include "sh_t32.h"
+
+#include <inttypes.h>
+#include <stdint.h>
+
+#include "sh_log.h"
+#include "sh_util.h"
+
+// https://developer.arm.com/documentation/ddi0406/latest
+// https://developer.arm.com/documentation/ddi0597/latest
+
+typedef enum {
+  IGNORED = 0,
+  B_T3,
+  B_T4,
+  BL_IMM_T1,
+  BLX_IMM_T2,
+  ADR_T2,
+  ADR_T3,
+  LDR_LIT_T2,
+  LDR_LIT_PC_T2,
+  LDRB_LIT_T1,
+  LDRD_LIT_T1,
+  LDRH_LIT_T1,
+  LDRSB_LIT_T1,
+  LDRSH_LIT_T1,
+  PLD_LIT_T1,
+  PLI_LIT_T3,
+  TBB_T1,
+  TBH_T1,
+  VLDR_LIT_T1
+} sh_t32_type_t;
+
+static sh_t32_type_t sh_t32_get_type(uint32_t inst) {
+  if (((inst & 0xF800D000) == 0xF0008000) && ((inst & 0x03800000u) != 0x03800000u))
+    return B_T3;
+  else if ((inst & 0xF800D000) == 0xF0009000)
+    return B_T4;
+  else if ((inst & 0xF800D000) == 0xF000D000)
+    return BL_IMM_T1;
+  else if ((inst & 0xF800D000) == 0xF000C000)
+    return BLX_IMM_T2;
+  else if ((inst & 0xFBFF8000) == 0xF2AF0000)
+    return ADR_T2;
+  else if ((inst & 0xFBFF8000) == 0xF20F0000)
+    return ADR_T3;
+  else if ((inst & 0xFF7F0000) == 0xF85F0000)
+    return ((inst & 0x0000F000u) == 0x0000F000) ? LDR_LIT_PC_T2 : LDR_LIT_T2;
+  else if (((inst & 0xFF7F0000) == 0xF81F0000) && ((inst & 0xF000u) != 0xF000u))
+    return LDRB_LIT_T1;
+  else if ((inst & 0xFF7F0000) == 0xE95F0000)
+    return LDRD_LIT_T1;
+  else if (((inst & 0xFF7F0000) == 0xF83F0000) && ((inst & 0xF000u) != 0xF000u))
+    return LDRH_LIT_T1;
+  else if (((inst & 0xFF7F0000) == 0xF91F0000) && ((inst & 0xF000u) != 0xF000u))
+    return LDRSB_LIT_T1;
+  else if (((inst & 0xFF7F0000) == 0xF93F0000) && ((inst & 0xF000u) != 0xF000u))
+    return LDRSH_LIT_T1;
+  else if ((inst & 0xFF7FF000) == 0xF81FF000)
+    return PLD_LIT_T1;
+  else if ((inst & 0xFF7FF000) == 0xF91FF000)
+    return PLI_LIT_T3;
+  else if ((inst & 0xFFF0FFF0) == 0xE8D0F000)
+    return TBB_T1;
+  else if ((inst & 0xFFF0FFF0) == 0xE8D0F010)
+    return TBH_T1;
+  else if ((inst & 0xFF3F0C00) == 0xED1F0800)
+    return VLDR_LIT_T1;
+  else
+    return IGNORED;
+}
+
+size_t sh_t32_get_rewrite_inst_len(uint16_t high_inst, uint16_t low_inst) {
+  static uint8_t map[] = {
+      4,   // IGNORED
+      12,  // B_T3
+      8,   // B_T4
+      12,  // BL_IMM_T1
+      12,  // BLX_IMM_T2
+      12,  // ADR_T2
+      12,  // ADR_T3
+      16,  // LDR_LIT_T2
+      24,  // LDR_LIT_PC_T2
+      16,  // LDRB_LIT_T1
+      16,  // LDRD_LIT_T1
+      16,  // LDRH_LIT_T1
+      16,  // LDRSB_LIT_T1
+      16,  // LDRSH_LIT_T1
+      20,  // PLD_LIT_T1
+      20,  // PLI_LIT_T3
+      32,  // TBB_T1
+      32,  // TBH_T1
+      24   // VLDR_LIT_T1
+  };
+
+  uint32_t inst = (uint32_t)(high_inst << 16u) | low_inst;
+  return (size_t)(map[sh_t32_get_type(inst)]);
+}
+
+static size_t sh_t32_rewrite_b(uint16_t *buf, uint16_t high_inst, uint16_t low_inst, uintptr_t pc,
+                               sh_t32_type_t type, sh_txx_rewrite_info_t *rinfo) {
+  uint32_t j1 = SH_UTIL_GET_BIT_16(low_inst, 13);
+  uint32_t j2 = SH_UTIL_GET_BIT_16(low_inst, 11);
+  uint32_t s = SH_UTIL_GET_BIT_16(high_inst, 10);
+  uint32_t i1 = !(j1 ^ s);
+  uint32_t i2 = !(j2 ^ s);
+
+  uint32_t addr;
+  if (type == B_T3) {
+    uint32_t x =
+        (s << 20u) | (j2 << 19u) | (j1 << 18u) | ((high_inst & 0x3Fu) << 12u) | ((low_inst & 0x7FFu) << 1u);
+    uint32_t imm32 = SH_UTIL_SIGN_EXTEND_32(x, 21u);
+    addr = SH_UTIL_SET_BIT0(pc + imm32);  // thumb -> thumb
+  } else if (type == B_T4) {
+    uint32_t x =
+        (s << 24u) | (i1 << 23u) | (i2 << 22u) | ((high_inst & 0x3FFu) << 12u) | ((low_inst & 0x7FFu) << 1u);
+    uint32_t imm32 = SH_UTIL_SIGN_EXTEND_32(x, 25u);
+    addr = SH_UTIL_SET_BIT0(pc + imm32);  // thumb -> thumb
+  } else if (type == BL_IMM_T1) {
+    uint32_t x =
+        (s << 24u) | (i1 << 23u) | (i2 << 22u) | ((high_inst & 0x3FFu) << 12u) | ((low_inst & 0x7FFu) << 1u);
+    uint32_t imm32 = SH_UTIL_SIGN_EXTEND_32(x, 25u);
+    addr = SH_UTIL_SET_BIT0(pc + imm32);  // thumb -> thumb
+  } else                                  // type == BLX_IMM_T2
+  {
+    uint32_t x =
+        (s << 24u) | (i1 << 23u) | (i2 << 22u) | ((high_inst & 0x3FFu) << 12u) | ((low_inst & 0x7FEu) << 1u);
+    uint32_t imm32 = SH_UTIL_SIGN_EXTEND_32(x, 25u);
+    // In BL and BLX instructions, only when the target instruction set is "arm",
+    // you need to do 4-byte alignment for PC.
+    addr = SH_UTIL_ALIGN_4(pc) + imm32;  // thumb -> arm, align4
+  }
+  addr = sh_txx_fix_addr(addr, rinfo);
+
+  size_t idx = 0;
+  if (type == B_T3) {
+    uint32_t cond = SH_UTIL_GET_BITS_16(high_inst, 9, 6);
+    buf[idx++] = 0xD000u | (uint16_t)(cond << 8u);  // B<c> #0
+    buf[idx++] = 0xE003;                            // B #6
+  } else if (type == BL_IMM_T1 || type == BLX_IMM_T2) {
+    buf[idx++] = 0xF20F;  // ADD LR, PC, #9
+    buf[idx++] = 0x0E09;  // ...
+  }
+  buf[idx++] = 0xF8DF;  // LDR.W PC, [PC]
+  buf[idx++] = 0xF000;  // ...
+  buf[idx++] = addr & 0xFFFFu;
+  buf[idx++] = addr >> 16u;
+  return idx * 2;  // 8 or 12
+}
+
+static size_t sh_t32_rewrite_adr(uint16_t *buf, uint16_t high_inst, uint16_t low_inst, uintptr_t pc,
+                                 sh_t32_type_t type, sh_txx_rewrite_info_t *rinfo) {
+  uint32_t rt = SH_UTIL_GET_BITS_16(low_inst, 11, 8);  // r0 - r14
+  uint32_t i = SH_UTIL_GET_BIT_16(high_inst, 10);
+  uint32_t imm3 = SH_UTIL_GET_BITS_16(low_inst, 14, 12);
+  uint32_t imm8 = SH_UTIL_GET_BITS_16(low_inst, 7, 0);
+  uint32_t imm32 = (i << 11u) | (imm3 << 8u) | imm8;
+  uint32_t addr = (type == ADR_T2 ? (SH_UTIL_ALIGN_4(pc) - imm32) : (SH_UTIL_ALIGN_4(pc) + imm32));
+  if (sh_txx_is_addr_need_fix(addr, rinfo)) return 0;  // rewrite failed
+
+  buf[0] = 0xF8DF;                      // LDR.W Rt, [PC, #4]
+  buf[1] = (uint16_t)(rt << 12u) + 4u;  // ...
+  buf[2] = 0xE002;                      // B #4
+  buf[3] = 0xBF00;                      // NOP
+  buf[4] = addr & 0xFFFFu;
+  buf[5] = addr >> 16u;
+  return 12;
+}
+
+static size_t sh_t32_rewrite_ldr(uint16_t *buf, uint16_t high_inst, uint16_t low_inst, uintptr_t pc,
+                                 sh_t32_type_t type, sh_txx_rewrite_info_t *rinfo) {
+  uint32_t u = SH_UTIL_GET_BIT_16(high_inst, 7);
+  uint32_t rt = SH_UTIL_GET_BITS_16(low_inst, 15, 12);  // r0 - r15
+  uint32_t rt2 = 0;                                     // r0 - r15
+  uint32_t addr;
+
+  if (type == LDRD_LIT_T1) {
+    rt2 = SH_UTIL_GET_BITS_16(low_inst, 11, 8);
+    uint32_t imm8 = SH_UTIL_GET_BITS_16(low_inst, 7, 0);
+    addr = (u ? SH_UTIL_ALIGN_4(pc) + (imm8 << 2u) : SH_UTIL_ALIGN_4(pc) - (imm8 << 2u));
+  } else {
+    uint32_t imm12 = (uint32_t)SH_UTIL_GET_BITS_16(low_inst, 11, 0);
+    addr = (u ? SH_UTIL_ALIGN_4(pc) + imm12 : SH_UTIL_ALIGN_4(pc) - imm12);
+  }
+  if (sh_txx_is_addr_need_fix(addr, rinfo)) return 0;  // rewrite failed
+
+  if (type == LDR_LIT_PC_T2 && rt == 0xF)  // Rt == PC
+  {
+    buf[0] = 0xB403;          // PUSH {R0, R1}
+    buf[1] = 0xBF00;          // NOP
+    buf[2] = 0xF8DF;          // LDR.W R0, [PC, #4]
+    buf[3] = 0x0004;          // ...
+    buf[4] = 0xE002;          // B #4
+    buf[5] = 0xBF00;          // NOP
+    buf[6] = addr & 0xFFFFu;  //
+    buf[7] = addr >> 16u;     //
+    buf[8] = 0xF8D0;          // LDR.W R0, [R0]
+    buf[9] = 0x0000;          // ...
+    buf[10] = 0x9001;         // STR R0, [SP, #4]
+    buf[11] = 0xBD01;         // POP {R0, PC}
+    return 24;
+  } else {
+    buf[0] = 0xF8DF;                      // LDR.W Rt, [PC, #4]
+    buf[1] = (uint16_t)(rt << 12u) | 4u;  // ...
+    buf[2] = 0xE002;                      // B #4
+    buf[3] = 0xBF00;                      // NOP
+    buf[4] = addr & 0xFFFFu;
+    buf[5] = addr >> 16u;
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wswitch"
+    switch (type) {
+      case LDR_LIT_T2:
+        buf[6] = (uint16_t)(0xF8D0 + rt);  // LDR.W Rt, [Rt]
+        buf[7] = (uint16_t)(rt << 12u);    // ...
+        break;
+      case LDRB_LIT_T1:
+        buf[6] = (uint16_t)(0xF890 + rt);  // LDRB.W Rt, [Rt]
+        buf[7] = (uint16_t)(rt << 12u);    // ...
+        break;
+      case LDRD_LIT_T1:
+        buf[6] = (uint16_t)(0xE9D0 + rt);                        // LDRD Rt, Rt2, [Rt]
+        buf[7] = (uint16_t)(rt << 12u) + (uint16_t)(rt2 << 8u);  // ...
+        break;
+      case LDRH_LIT_T1:
+        buf[6] = (uint16_t)(0xF8B0 + rt);  // LDRH.W Rt, [Rt]
+        buf[7] = (uint16_t)(rt << 12u);    // ...
+        break;
+      case LDRSB_LIT_T1:
+        buf[6] = (uint16_t)(0xF990 + rt);  // LDRSB.W Rt, [Rt]
+        buf[7] = (uint16_t)(rt << 12u);    // ...
+        break;
+      case LDRSH_LIT_T1:
+        buf[6] = (uint16_t)(0xF9B0 + rt);  // LDRSH.W Rt, [Rt]
+        buf[7] = (uint16_t)(rt << 12u);    // ...
+        break;
+    }
+#pragma clang diagnostic pop
+    return 16;
+  }
+}
+
+static size_t sh_t32_rewrite_pl(uint16_t *buf, uint16_t high_inst, uint16_t low_inst, uintptr_t pc,
+                                sh_t32_type_t type, sh_txx_rewrite_info_t *rinfo) {
+  uint32_t u = SH_UTIL_GET_BIT_16(high_inst, 7);
+  uint32_t imm12 = SH_UTIL_GET_BITS_16(low_inst, 11, 0);
+  uint32_t addr = (u ? SH_UTIL_ALIGN_4(pc) + imm12 : SH_UTIL_ALIGN_4(pc) - imm12);
+  addr = sh_txx_fix_addr(addr, rinfo);
+
+  buf[0] = 0xB401;  // PUSH {R0}
+  buf[1] = 0xBF00;  // NOP
+  buf[2] = 0xF8DF;  // LDR.W R0, [PC, #8]
+  buf[3] = 0x0008;  // ...
+  if (type == PLD_LIT_T1) {
+    buf[4] = 0xF890;  // PLD [R0]
+    buf[5] = 0xF000;  // ...
+  } else {
+    buf[4] = 0xF990;  // PLI [R0]
+    buf[5] = 0xF000;  // ...
+  }
+  buf[6] = 0xBC01;  // POP {R0}
+  buf[7] = 0xE001;  // B #2
+  buf[8] = addr & 0xFFFFu;
+  buf[9] = addr >> 16u;
+  return 20;
+}
+
+static size_t sh_t32_rewrite_tb(uint16_t *buf, uint16_t high_inst, uint16_t low_inst, uintptr_t pc,
+                                sh_t32_type_t type, sh_txx_rewrite_info_t *rinfo) {
+  // If TBB/TBH is not the last instruction that needs to be rewritten,
+  // the rewriting can NOT be completed.
+  uintptr_t target_addr = SH_UTIL_CLEAR_BIT0(pc - 4);
+  if (target_addr + 4 != rinfo->end_addr) return 0;  // rewrite failed
+
+  uint32_t rn = SH_UTIL_GET_BITS_16(high_inst, 3, 0);
+  uint32_t rm = SH_UTIL_GET_BITS_16(low_inst, 3, 0);
+  uint32_t rx, ry;  // r0 - r7
+  for (rx = 7;; --rx)
+    if (rx != rn && rx != rm) break;
+  for (ry = 7;; --ry)
+    if (ry != rn && ry != rm && ry != rx) break;
+
+  buf[0] = (uint16_t)(0xB500u | (1u << rx) | (1u << ry));  // PUSH {Rx, Ry, LR}
+  buf[1] = 0xBF00;                                         // NOP
+  buf[2] = 0xF8DF;                                         // LDR.W Rx, [PC, #20]
+  buf[3] = (uint16_t)(rx << 12u) | 20u;                    // ...
+  if (type == TBB_T1) {
+    buf[4] = (uint16_t)(0xEB00u | (rn == 0xF ? rx : rn));  // ADD.W Ry, Rx|Rn, Rm
+    buf[5] = (uint16_t)(0x0000u | (ry << 8u) | rm);        // ...
+    buf[6] = (uint16_t)(0x7800u | (ry << 3u) | ry);        // LDRB Ry, [Ry]
+    buf[7] = 0xBF00;                                       // NOP
+  } else {
+    buf[4] = (uint16_t)(0xEB00u | (rn == 0xF ? rx : rn));  // ADD.W Ry, Rx|Rn, Rm, LSL #1
+    buf[5] = (uint16_t)(0x0040u | (ry << 8u) | rm);        // ...
+    buf[6] = (uint16_t)(0x8800u | (ry << 3u) | ry);        // LDRH Ry, [Ry]
+    buf[7] = 0xBF00;                                       // NOP
+  }
+  buf[8] = (uint16_t)(0xEB00u | rx);                        // ADD Rx, Rx, Ry, LSL #1
+  buf[9] = (uint16_t)(0x0040u | (rx << 8u) | ry);           // ...
+  buf[10] = (uint16_t)(0x3001u | (rx << 8u));               // ADD Rx, #1
+  buf[11] = (uint16_t)(0x9002u | (rx << 8u));               // STR Rx, [SP, #8]
+  buf[12] = (uint16_t)(0xBD00u | (1u << rx) | (1u << ry));  // POP {Rx, Ry, PC}
+  buf[13] = 0xBF00;                                         // NOP
+  buf[14] = pc & 0xFFFFu;
+  buf[15] = pc >> 16u;
+  return 32;
+}
+
+static size_t sh_t32_rewrite_vldr(uint16_t *buf, uint16_t high_inst, uint16_t low_inst, uintptr_t pc,
+                                  sh_txx_rewrite_info_t *rinfo) {
+  uint32_t u = SH_UTIL_GET_BIT_16(high_inst, 7);
+  uint32_t D = SH_UTIL_GET_BIT_16(high_inst, 6);
+  uint32_t vd = SH_UTIL_GET_BITS_16(low_inst, 15, 12);
+  uint32_t size = SH_UTIL_GET_BITS_16(low_inst, 9, 8);
+  uint32_t imm8 = SH_UTIL_GET_BITS_16(low_inst, 7, 0);
+  uint32_t esize = (8u << size);
+  uint32_t imm32 = (esize == 16 ? imm8 << 1u : imm8 << 2u);
+  uint32_t addr = (u ? SH_UTIL_ALIGN_4(pc) + imm32 : SH_UTIL_ALIGN_4(pc) - imm32);
+  if (sh_txx_is_addr_need_fix(addr, rinfo)) return 0;  // rewrite failed
+
+  buf[0] = 0xB401;                                       // PUSH {R0}
+  buf[1] = 0xBF00;                                       // NOP
+  buf[2] = 0xF8DF;                                       // LDR.W R0, [PC, #4]
+  buf[3] = 0x0004;                                       // ...
+  buf[4] = 0xE002;                                       // B #4
+  buf[5] = 0xBF00;                                       // NOP
+  buf[6] = addr & 0xFFFFu;                               //
+  buf[7] = addr >> 16u;                                  //
+  buf[8] = (uint16_t)(0xED90u | D << 6u);                // VLDR Sd|Dd, [R0]
+  buf[9] = (uint16_t)(0x800u | vd << 12u | size << 8u);  // ...
+  buf[10] = 0xBC01;                                      // POP {R0}
+  buf[11] = 0xBF00;                                      // NOP
+  return 24;
+}
+
+size_t sh_t32_rewrite(uint16_t *buf, uint16_t high_inst, uint16_t low_inst, uintptr_t pc,
+                      sh_txx_rewrite_info_t *rinfo) {
+  uint32_t inst = (uint32_t)(high_inst << 16u) | low_inst;
+  sh_t32_type_t type = sh_t32_get_type(inst);
+  SH_LOG_INFO("t32 rewrite: type %d, high inst %" PRIx16 ", low inst %" PRIx16, type, high_inst, low_inst);
+
+  if (type == B_T3 || type == B_T4 || type == BL_IMM_T1 || type == BLX_IMM_T2)
+    return sh_t32_rewrite_b(buf, high_inst, low_inst, pc, type, rinfo);
+  else if (type == ADR_T2 || type == ADR_T3)
+    return sh_t32_rewrite_adr(buf, high_inst, low_inst, pc, type, rinfo);
+  else if (type == LDR_LIT_T2 || type == LDR_LIT_PC_T2 || type == LDRB_LIT_T1 || type == LDRD_LIT_T1 ||
+           type == LDRH_LIT_T1 || type == LDRSB_LIT_T1 || type == LDRSH_LIT_T1)
+    return sh_t32_rewrite_ldr(buf, high_inst, low_inst, pc, type, rinfo);
+  else if (type == PLD_LIT_T1 || type == PLI_LIT_T3)
+    return sh_t32_rewrite_pl(buf, high_inst, low_inst, pc, type, rinfo);
+  else if (type == TBB_T1 || type == TBH_T1)
+    return sh_t32_rewrite_tb(buf, high_inst, low_inst, pc, type, rinfo);
+  else if (type == VLDR_LIT_T1)
+    return sh_t32_rewrite_vldr(buf, high_inst, low_inst, pc, rinfo);
+  else {
+    // IGNORED
+    buf[0] = high_inst;
+    buf[1] = low_inst;
+    return 4;
+  }
+}
+
+size_t sh_t32_absolute_jump(uint16_t *buf, bool is_align4, uintptr_t addr) {
+  size_t i = 0;
+  if (!is_align4) buf[i++] = 0xBF00;  // NOP
+  buf[i++] = 0xF8DF;                  // LDR.W PC, [PC]
+  buf[i++] = 0xF000;                  // ...
+  buf[i++] = addr & 0xFFFFu;
+  buf[i++] = addr >> 16u;
+  return i * 2;
+}
+
+size_t sh_t32_relative_jump(uint16_t *buf, uintptr_t addr, uintptr_t pc) {
+  uint32_t imm32 = addr - pc;
+  uint32_t s = SH_UTIL_GET_BIT_32(imm32, 24);
+  uint32_t i1 = SH_UTIL_GET_BIT_32(imm32, 23);
+  uint32_t i2 = SH_UTIL_GET_BIT_32(imm32, 22);
+  uint32_t imm10 = SH_UTIL_GET_BITS_32(imm32, 21, 12);
+  uint32_t imm11 = SH_UTIL_GET_BITS_32(imm32, 11, 1);
+  uint32_t j1 = (!i1) ^ s;
+  uint32_t j2 = (!i2) ^ s;
+
+  buf[0] = (uint16_t)(0xF000u | (s << 10u) | imm10);
+  buf[1] = (uint16_t)(0x9000u | (j1 << 13u) | (j2 << 11u) | imm11);
+  return 4;
+}
diff --git a/app/src/main/cpp/shadowhook/arch/arm/sh_t32.h b/app/src/main/cpp/shadowhook/arch/arm/sh_t32.h
new file mode 100644
index 0000000000000000000000000000000000000000..1dc11797063e57aad53d00c38939bf2b7219a09b
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/arch/arm/sh_t32.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#pragma once
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "sh_txx.h"
+
+size_t sh_t32_get_rewrite_inst_len(uint16_t high_inst, uint16_t low_inst);
+size_t sh_t32_rewrite(uint16_t *buf, uint16_t high_inst, uint16_t low_inst, uintptr_t pc,
+                      sh_txx_rewrite_info_t *rinfo);
+
+size_t sh_t32_absolute_jump(uint16_t *buf, bool is_align4, uintptr_t addr);
+size_t sh_t32_relative_jump(uint16_t *buf, uintptr_t addr, uintptr_t pc);
diff --git a/app/src/main/cpp/shadowhook/arch/arm/sh_txx.c b/app/src/main/cpp/shadowhook/arch/arm/sh_txx.c
new file mode 100644
index 0000000000000000000000000000000000000000..79cefdb90f26afa312e5ae2d5436dcaa0249a5c5
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/arch/arm/sh_txx.c
@@ -0,0 +1,60 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#include "sh_txx.h"
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "sh_log.h"
+#include "sh_util.h"
+
+bool sh_txx_is_addr_need_fix(uintptr_t addr, sh_txx_rewrite_info_t *rinfo) {
+  return (rinfo->start_addr <= addr && addr < rinfo->end_addr);
+}
+
+uintptr_t sh_txx_fix_addr(uintptr_t addr, sh_txx_rewrite_info_t *rinfo) {
+  bool is_thumb = SH_UTIL_IS_THUMB(addr);
+
+  if (is_thumb) addr = SH_UTIL_CLEAR_BIT0(addr);
+
+  if (rinfo->start_addr <= addr && addr < rinfo->end_addr) {
+    uintptr_t cursor_addr = rinfo->start_addr;
+    size_t offset = 0;
+    for (size_t i = 0; i < rinfo->inst_lens_cnt; i++) {
+      if (cursor_addr >= addr) break;
+      cursor_addr += 2;
+      offset += rinfo->inst_lens[i];
+    }
+    uintptr_t fixed_addr = (uintptr_t)rinfo->buf + offset;
+    if (is_thumb) fixed_addr = SH_UTIL_SET_BIT0(fixed_addr);
+
+    SH_LOG_INFO("txx rewrite: fix addr %" PRIxPTR " -> %" PRIxPTR, addr, fixed_addr);
+    return fixed_addr;
+  }
+
+  if (is_thumb) addr = SH_UTIL_SET_BIT0(addr);
+  return addr;
+}
diff --git a/app/src/main/cpp/shadowhook/arch/arm/sh_txx.h b/app/src/main/cpp/shadowhook/arch/arm/sh_txx.h
new file mode 100644
index 0000000000000000000000000000000000000000..f5b9fae76ec80894da26502d65693dbb57d2268d
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/arch/arm/sh_txx.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#pragma once
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+typedef struct {
+  uintptr_t start_addr;
+  uintptr_t end_addr;
+  uint16_t *buf;
+  size_t buf_offset;
+  size_t inst_lens[13];  // 26 / 2 = 13
+  size_t inst_lens_cnt;
+} sh_txx_rewrite_info_t;
+
+bool sh_txx_is_addr_need_fix(uintptr_t addr, sh_txx_rewrite_info_t *rinfo);
+uintptr_t sh_txx_fix_addr(uintptr_t addr, sh_txx_rewrite_info_t *rinfo);
diff --git a/app/src/main/cpp/shadowhook/arch/arm64/sh_a64.c b/app/src/main/cpp/shadowhook/arch/arm64/sh_a64.c
new file mode 100644
index 0000000000000000000000000000000000000000..cd9546bdf7202c4706f81df020e06ca53c26b5e6
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/arch/arm64/sh_a64.c
@@ -0,0 +1,310 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#include "sh_a64.h"
+
+#include <inttypes.h>
+#include <sh_util.h>
+#include <stdint.h>
+
+#include "sh_log.h"
+
+// https://developer.arm.com/documentation/ddi0487/latest
+// https://developer.arm.com/documentation/ddi0602/latest
+
+typedef enum {
+  IGNORED = 0,
+  B,
+  B_COND,
+  BL,
+  ADR,
+  ADRP,
+  LDR_LIT_32,
+  LDR_LIT_64,
+  LDRSW_LIT,
+  PRFM_LIT,
+  LDR_SIMD_LIT_32,
+  LDR_SIMD_LIT_64,
+  LDR_SIMD_LIT_128,
+  CBZ,
+  CBNZ,
+  TBZ,
+  TBNZ
+} sh_a64_type_t;
+
+static sh_a64_type_t sh_a64_get_type(uint32_t inst) {
+  if ((inst & 0xFC000000) == 0x14000000)
+    return B;
+  else if ((inst & 0xFF000010) == 0x54000000)
+    return B_COND;
+  else if ((inst & 0xFC000000) == 0x94000000)
+    return BL;
+  else if ((inst & 0x9F000000) == 0x10000000)
+    return ADR;
+  else if ((inst & 0x9F000000) == 0x90000000)
+    return ADRP;
+  else if ((inst & 0xFF000000) == 0x18000000)
+    return LDR_LIT_32;
+  else if ((inst & 0xFF000000) == 0x58000000)
+    return LDR_LIT_64;
+  else if ((inst & 0xFF000000) == 0x98000000)
+    return LDRSW_LIT;
+  else if ((inst & 0xFF000000) == 0xD8000000)
+    return PRFM_LIT;
+  else if ((inst & 0xFF000000) == 0x1C000000)
+    return LDR_SIMD_LIT_32;
+  else if ((inst & 0xFF000000) == 0x5C000000)
+    return LDR_SIMD_LIT_64;
+  else if ((inst & 0xFF000000) == 0x9C000000)
+    return LDR_SIMD_LIT_128;
+  else if ((inst & 0x7F000000u) == 0x34000000)
+    return CBZ;
+  else if ((inst & 0x7F000000u) == 0x35000000)
+    return CBNZ;
+  else if ((inst & 0x7F000000u) == 0x36000000)
+    return TBZ;
+  else if ((inst & 0x7F000000u) == 0x37000000)
+    return TBNZ;
+  else
+    return IGNORED;
+}
+
+size_t sh_a64_get_rewrite_inst_len(uint32_t inst) {
+  static uint8_t map[] = {
+      4,   // IGNORED
+      20,  // B
+      28,  // B_COND
+      20,  // BL
+      16,  // ADR
+      16,  // ADRP
+      20,  // LDR_LIT_32
+      20,  // LDR_LIT_64
+      20,  // LDRSW_LIT
+      28,  // PRFM_LIT
+      28,  // LDR_SIMD_LIT_32
+      28,  // LDR_SIMD_LIT_64
+      28,  // LDR_SIMD_LIT_128
+      24,  // CBZ
+      24,  // CBNZ
+      24,  // TBZ
+      24   // TBNZ
+  };
+
+  return (size_t)(map[sh_a64_get_type(inst)]);
+}
+
+static bool sh_a64_is_addr_need_fix(uintptr_t addr, sh_a64_rewrite_info_t *rinfo) {
+  return (rinfo->start_addr <= addr && addr < rinfo->end_addr);
+}
+
+static uintptr_t sh_a64_fix_addr(uintptr_t addr, sh_a64_rewrite_info_t *rinfo) {
+  if (rinfo->start_addr <= addr && addr < rinfo->end_addr) {
+    uintptr_t cursor_addr = rinfo->start_addr;
+    size_t offset = 0;
+    for (size_t i = 0; i < rinfo->inst_lens_cnt; i++) {
+      if (cursor_addr >= addr) break;
+      cursor_addr += 4;
+      offset += rinfo->inst_lens[i];
+    }
+    uintptr_t fixed_addr = (uintptr_t)rinfo->buf + offset;
+    SH_LOG_INFO("a64 rewrite: fix addr %" PRIxPTR " -> %" PRIxPTR, addr, fixed_addr);
+    return fixed_addr;
+  }
+
+  return addr;
+}
+
+static size_t sh_a64_rewrite_b(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a64_type_t type,
+                               sh_a64_rewrite_info_t *rinfo) {
+  uint64_t imm64;
+  if (type == B_COND) {
+    uint64_t imm19 = SH_UTIL_GET_BITS_32(inst, 23, 5);
+    imm64 = SH_UTIL_SIGN_EXTEND_64(imm19 << 2u, 21u);
+  } else {
+    uint64_t imm26 = SH_UTIL_GET_BITS_32(inst, 25, 0);
+    imm64 = SH_UTIL_SIGN_EXTEND_64(imm26 << 2u, 28u);
+  }
+  uint64_t addr = pc + imm64;
+  addr = sh_a64_fix_addr(addr, rinfo);
+
+  size_t idx = 0;
+  if (type == B_COND) {
+    buf[idx++] = (inst & 0xFF00001F) | 0x40u;  // B.<cond> #8
+    buf[idx++] = 0x14000006;                   // B #24
+  }
+  buf[idx++] = 0x58000051;  // LDR X17, #8
+  buf[idx++] = 0x14000003;  // B #12
+  buf[idx++] = addr & 0xFFFFFFFF;
+  buf[idx++] = addr >> 32u;
+  if (type == BL)
+    buf[idx++] = 0xD63F0220;  // BLR X17
+  else
+    buf[idx++] = 0xD61F0220;  // BR X17
+  return idx * 4;             // 20 or 28
+}
+
+static size_t sh_a64_rewrite_adr(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a64_type_t type,
+                                 sh_a64_rewrite_info_t *rinfo) {
+  uint32_t xd = SH_UTIL_GET_BITS_32(inst, 4, 0);
+  uint64_t immlo = SH_UTIL_GET_BITS_32(inst, 30, 29);
+  uint64_t immhi = SH_UTIL_GET_BITS_32(inst, 23, 5);
+  uint64_t addr;
+  if (type == ADR)
+    addr = pc + SH_UTIL_SIGN_EXTEND_64((immhi << 2u) | immlo, 21u);
+  else  // ADRP
+    addr = (pc & 0xFFFFFFFFFFFFF000) + SH_UTIL_SIGN_EXTEND_64((immhi << 14u) | (immlo << 12u), 33u);
+  if (sh_a64_is_addr_need_fix(addr, rinfo)) return 0;  // rewrite failed
+
+  buf[0] = 0x58000040u | xd;  // LDR Xd, #8
+  buf[1] = 0x14000003;        // B #12
+  buf[2] = addr & 0xFFFFFFFF;
+  buf[3] = addr >> 32u;
+  return 16;
+}
+
+static size_t sh_a64_rewrite_ldr(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a64_type_t type,
+                                 sh_a64_rewrite_info_t *rinfo) {
+  uint32_t rt = SH_UTIL_GET_BITS_32(inst, 4, 0);
+  uint64_t imm19 = SH_UTIL_GET_BITS_32(inst, 23, 5);
+  uint64_t offset = SH_UTIL_SIGN_EXTEND_64((imm19 << 2u), 21u);
+  uint64_t addr = pc + offset;
+
+  if (sh_a64_is_addr_need_fix(addr, rinfo)) {
+    if (type != PRFM_LIT) return 0;  // rewrite failed
+    addr = sh_a64_fix_addr(addr, rinfo);
+  }
+
+  if (type == LDR_LIT_32 || type == LDR_LIT_64 || type == LDRSW_LIT) {
+    buf[0] = 0x58000060u | rt;  // LDR Xt, #12
+    if (type == LDR_LIT_32)
+      buf[1] = 0xB9400000 | rt | (rt << 5u);  // LDR Wt, [Xt]
+    else if (type == LDR_LIT_64)
+      buf[1] = 0xF9400000 | rt | (rt << 5u);  // LDR Xt, [Xt]
+    else
+      // LDRSW_LIT
+      buf[1] = 0xB9800000 | rt | (rt << 5u);  // LDRSW Xt, [Xt]
+    buf[2] = 0x14000003;                      // B #12
+    buf[3] = addr & 0xFFFFFFFF;
+    buf[4] = addr >> 32u;
+    return 20;
+  } else {
+    buf[0] = 0xA93F47F0;  // STP X16, X17, [SP, -0x10]
+    buf[1] = 0x58000091;  // LDR X17, #16
+    if (type == PRFM_LIT)
+      buf[2] = 0xF9800220 | rt;  // PRFM Rt, [X17]
+    else if (type == LDR_SIMD_LIT_32)
+      buf[2] = 0xBD400220 | rt;  // LDR St, [X17]
+    else if (type == LDR_SIMD_LIT_64)
+      buf[2] = 0xFD400220 | rt;  // LDR Dt, [X17]
+    else
+      // LDR_SIMD_LIT_128
+      buf[2] = 0x3DC00220u | rt;  // LDR Qt, [X17]
+    buf[3] = 0xF85F83F1;          // LDR X17, [SP, -0x8]
+    buf[4] = 0x14000003;          // B #12
+    buf[5] = addr & 0xFFFFFFFF;
+    buf[6] = addr >> 32u;
+    return 28;
+  }
+}
+
+static size_t sh_a64_rewrite_cb(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a64_rewrite_info_t *rinfo) {
+  uint64_t imm19 = SH_UTIL_GET_BITS_32(inst, 23, 5);
+  uint64_t offset = SH_UTIL_SIGN_EXTEND_64((imm19 << 2u), 21u);
+  uint64_t addr = pc + offset;
+  addr = sh_a64_fix_addr(addr, rinfo);
+
+  buf[0] = (inst & 0xFF00001F) | 0x40u;  // CB(N)Z Rt, #8
+  buf[1] = 0x14000005;                   // B #20
+  buf[2] = 0x58000051;                   // LDR X17, #8
+  buf[3] = 0xd61f0220;                   // BR X17
+  buf[4] = addr & 0xFFFFFFFF;
+  buf[5] = addr >> 32u;
+  return 24;
+}
+
+static size_t sh_a64_rewrite_tb(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a64_rewrite_info_t *rinfo) {
+  uint64_t imm14 = SH_UTIL_GET_BITS_32(inst, 18, 5);
+  uint64_t offset = SH_UTIL_SIGN_EXTEND_64((imm14 << 2u), 16u);
+  uint64_t addr = pc + offset;
+  addr = sh_a64_fix_addr(addr, rinfo);
+
+  buf[0] = (inst & 0xFFF8001F) | 0x40u;  // TB(N)Z Rt, #<imm>, #8
+  buf[1] = 0x14000005;                   // B #20
+  buf[2] = 0x58000051;                   // LDR X17, #8
+  buf[3] = 0xd61f0220;                   // BR X17
+  buf[4] = addr & 0xFFFFFFFF;
+  buf[5] = addr >> 32u;
+  return 24;
+}
+
+size_t sh_a64_rewrite(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a64_rewrite_info_t *rinfo) {
+  sh_a64_type_t type = sh_a64_get_type(inst);
+  SH_LOG_INFO("a64 rewrite: type %d, inst %" PRIx32, type, inst);
+
+  if (type == B || type == B_COND || type == BL)
+    return sh_a64_rewrite_b(buf, inst, pc, type, rinfo);
+  else if (type == ADR || type == ADRP)
+    return sh_a64_rewrite_adr(buf, inst, pc, type, rinfo);
+  else if (type == LDR_LIT_32 || type == LDR_LIT_64 || type == LDRSW_LIT || type == PRFM_LIT ||
+           type == LDR_SIMD_LIT_32 || type == LDR_SIMD_LIT_64 || type == LDR_SIMD_LIT_128)
+    return sh_a64_rewrite_ldr(buf, inst, pc, type, rinfo);
+  else if (type == CBZ || type == CBNZ)
+    return sh_a64_rewrite_cb(buf, inst, pc, rinfo);
+  else if (type == TBZ || type == TBNZ)
+    return sh_a64_rewrite_tb(buf, inst, pc, rinfo);
+  else {
+    // IGNORED
+    buf[0] = inst;
+    return 4;
+  }
+}
+
+size_t sh_a64_absolute_jump_with_br(uint32_t *buf, uintptr_t addr) {
+  buf[0] = 0x58000051;  // LDR X17, #8
+  buf[1] = 0xd61f0220;  // BR X17
+  buf[2] = addr & 0xFFFFFFFF;
+  buf[3] = addr >> 32u;
+  return 16;
+}
+
+// Use RET instead of BR to bypass arm64 BTI.
+//
+// ref:
+// https://developer.arm.com/documentation/102433/0100/Jump-oriented-programming
+// https://developer.arm.com/documentation/ddi0602/2023-06/Base-Instructions/BTI--Branch-Target-Identification-?lang=en
+// https://github.com/torvalds/linux/commit/8ef8f360cf30be12382f89ff48a57fbbd9b31c14
+// https://android-review.googlesource.com/c/platform/bionic/+/1242754
+// https://developer.android.com/ndk/guides/abis#armv9_enabling_pac_and_bti_for_cc
+// https://developer.arm.com/documentation/100067/0612/armclang-Command-line-Options/-mbranch-protection
+size_t sh_a64_absolute_jump_with_ret(uint32_t *buf, uintptr_t addr) {
+  buf[0] = 0x58000051;  // LDR X17, #8
+  buf[1] = 0xd65f0220;  // RET X17
+  buf[2] = addr & 0xFFFFFFFF;
+  buf[3] = addr >> 32u;
+  return 16;
+}
+
+size_t sh_a64_relative_jump(uint32_t *buf, uintptr_t addr, uintptr_t pc) {
+  buf[0] = 0x14000000u | (((addr - pc) & 0x0FFFFFFFu) >> 2u);  // B <label>
+  return 4;
+}
diff --git a/app/src/main/cpp/shadowhook/arch/arm64/sh_a64.h b/app/src/main/cpp/shadowhook/arch/arm64/sh_a64.h
new file mode 100644
index 0000000000000000000000000000000000000000..a51cd2e2fb575edc1e8de87faf45b51bc34ea3df
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/arch/arm64/sh_a64.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#pragma once
+#include <stddef.h>
+#include <stdint.h>
+
+#include "sh_inst.h"
+
+typedef struct {
+  uintptr_t start_addr;
+  uintptr_t end_addr;
+  uint32_t *buf;
+  size_t buf_offset;
+  size_t inst_lens[4];
+  size_t inst_lens_cnt;
+} sh_a64_rewrite_info_t;
+
+size_t sh_a64_get_rewrite_inst_len(uint32_t inst);
+size_t sh_a64_rewrite(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a64_rewrite_info_t *rinfo);
+
+size_t sh_a64_absolute_jump_with_br(uint32_t *buf, uintptr_t addr);
+size_t sh_a64_absolute_jump_with_ret(uint32_t *buf, uintptr_t addr);
+size_t sh_a64_relative_jump(uint32_t *buf, uintptr_t addr, uintptr_t pc);
diff --git a/app/src/main/cpp/shadowhook/arch/arm64/sh_inst.c b/app/src/main/cpp/shadowhook/arch/arm64/sh_inst.c
new file mode 100644
index 0000000000000000000000000000000000000000..7474a6c811ccf3cc9910adc680a1ba57ae300b0a
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/arch/arm64/sh_inst.c
@@ -0,0 +1,203 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#include "sh_inst.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/mman.h>
+
+#include "sh_a64.h"
+#include "sh_config.h"
+#include "sh_enter.h"
+#include "sh_exit.h"
+#include "sh_log.h"
+#include "sh_sig.h"
+#include "sh_util.h"
+#include "shadowhook.h"
+#include "xdl.h"
+
+static int sh_inst_hook_rewrite(sh_inst_t *self, uintptr_t target_addr, uintptr_t *orig_addr,
+                                uintptr_t *orig_addr2) {
+  // backup original instructions (length: 4 or 16)
+  memcpy((void *)(self->backup), (void *)target_addr, self->backup_len);
+
+  // package the information passed to rewrite
+  sh_a64_rewrite_info_t rinfo;
+  rinfo.start_addr = target_addr;
+  rinfo.end_addr = target_addr + self->backup_len;
+  rinfo.buf = (uint32_t *)self->enter_addr;
+  rinfo.buf_offset = 0;
+  rinfo.inst_lens_cnt = self->backup_len / 4;
+  for (uintptr_t i = 0; i < self->backup_len; i += 4)
+    rinfo.inst_lens[i / 4] = sh_a64_get_rewrite_inst_len(*((uint32_t *)(target_addr + i)));
+
+  // rewrite original instructions (fill in enter)
+  uintptr_t pc = target_addr;
+  for (uintptr_t i = 0; i < self->backup_len; i += 4, pc += 4) {
+    size_t offset = sh_a64_rewrite((uint32_t *)(self->enter_addr + rinfo.buf_offset),
+                                   *((uint32_t *)(target_addr + i)), pc, &rinfo);
+    if (0 == offset) return SHADOWHOOK_ERRNO_HOOK_REWRITE_FAILED;
+    rinfo.buf_offset += offset;
+  }
+
+  // absolute jump back to remaining original instructions (fill in enter)
+  rinfo.buf_offset += sh_a64_absolute_jump_with_ret((uint32_t *)(self->enter_addr + rinfo.buf_offset),
+                                                    target_addr + self->backup_len);
+  sh_util_clear_cache(self->enter_addr, rinfo.buf_offset);
+
+  // save original function address
+  if (NULL != orig_addr) __atomic_store_n(orig_addr, self->enter_addr, __ATOMIC_SEQ_CST);
+  if (NULL != orig_addr2) __atomic_store_n(orig_addr2, self->enter_addr, __ATOMIC_SEQ_CST);
+  return 0;
+}
+
+#ifdef SH_CONFIG_TRY_WITH_EXIT
+
+// B: [-128M, +128M - 4]
+#define SH_INST_A64_B_RANGE_LOW  (134217728)
+#define SH_INST_A64_B_RANGE_HIGH (134217724)
+
+static int sh_inst_hook_with_exit(sh_inst_t *self, uintptr_t target_addr, xdl_info_t *dlinfo,
+                                  uintptr_t new_addr, uintptr_t *orig_addr, uintptr_t *orig_addr2) {
+  int r;
+  uintptr_t pc = target_addr;
+  self->backup_len = 4;
+
+  if (dlinfo->dli_ssize < self->backup_len) return SHADOWHOOK_ERRNO_HOOK_SYMSZ;
+
+  // alloc an exit for absolute jump
+  sh_a64_absolute_jump_with_br(self->exit, new_addr);
+  if (0 !=
+      (r = sh_exit_alloc(&self->exit_addr, (uint16_t *)&self->exit_type, pc, dlinfo, (uint8_t *)(self->exit),
+                         sizeof(self->exit), SH_INST_A64_B_RANGE_LOW, SH_INST_A64_B_RANGE_HIGH)))
+    return r;
+
+  // rewrite
+  if (0 != sh_util_mprotect(target_addr, self->backup_len, PROT_READ | PROT_WRITE | PROT_EXEC)) {
+    r = SHADOWHOOK_ERRNO_MPROT;
+    goto err;
+  }
+  SH_SIG_TRY(SIGSEGV, SIGBUS) {
+    r = sh_inst_hook_rewrite(self, target_addr, orig_addr, orig_addr2);
+  }
+  SH_SIG_CATCH() {
+    r = SHADOWHOOK_ERRNO_HOOK_REWRITE_CRASH;
+    goto err;
+  }
+  SH_SIG_EXIT
+  if (0 != r) goto err;
+
+  // relative jump to the exit by overwriting the head of original function
+  sh_a64_relative_jump(self->trampo, self->exit_addr, pc);
+  __atomic_thread_fence(__ATOMIC_SEQ_CST);
+  if (0 != (r = sh_util_write_inst(target_addr, self->trampo, self->backup_len))) goto err;
+
+  SH_LOG_INFO("a64: hook (WITH EXIT) OK. target %" PRIxPTR " -> exit %" PRIxPTR " -> new %" PRIxPTR
+              " -> enter %" PRIxPTR " -> remaining %" PRIxPTR,
+              target_addr, self->exit_addr, new_addr, self->enter_addr, target_addr + self->backup_len);
+  return 0;
+
+err:
+  sh_exit_free(self->exit_addr, (uint16_t)self->exit_type, (uint8_t *)(self->exit), sizeof(self->exit));
+  self->exit_addr = 0;  // this is a flag for with-exit or without-exit
+  return r;
+}
+#endif
+
+static int sh_inst_hook_without_exit(sh_inst_t *self, uintptr_t target_addr, xdl_info_t *dlinfo,
+                                     uintptr_t new_addr, uintptr_t *orig_addr, uintptr_t *orig_addr2) {
+  int r;
+  self->backup_len = 16;
+
+  if (dlinfo->dli_ssize < self->backup_len) return SHADOWHOOK_ERRNO_HOOK_SYMSZ;
+
+  // rewrite
+  if (0 != sh_util_mprotect(target_addr, self->backup_len, PROT_READ | PROT_WRITE | PROT_EXEC))
+    return SHADOWHOOK_ERRNO_MPROT;
+  SH_SIG_TRY(SIGSEGV, SIGBUS) {
+    r = sh_inst_hook_rewrite(self, target_addr, orig_addr, orig_addr2);
+  }
+  SH_SIG_CATCH() {
+    return SHADOWHOOK_ERRNO_HOOK_REWRITE_CRASH;
+  }
+  SH_SIG_EXIT
+  if (0 != r) return r;
+
+  // absolute jump to new function address by overwriting the head of original function
+  sh_a64_absolute_jump_with_br(self->trampo, new_addr);
+  __atomic_thread_fence(__ATOMIC_SEQ_CST);
+  if (0 != (r = sh_util_write_inst(target_addr, self->trampo, self->backup_len))) return r;
+
+  SH_LOG_INFO("a64: hook (WITHOUT EXIT) OK. target %" PRIxPTR " -> new %" PRIxPTR " -> enter %" PRIxPTR
+              " -> remaining %" PRIxPTR,
+              target_addr, new_addr, self->enter_addr, target_addr + self->backup_len);
+  return 0;
+}
+
+int sh_inst_hook(sh_inst_t *self, uintptr_t target_addr, xdl_info_t *dlinfo, uintptr_t new_addr,
+                 uintptr_t *orig_addr, uintptr_t *orig_addr2) {
+  self->enter_addr = sh_enter_alloc();
+  if (0 == self->enter_addr) return SHADOWHOOK_ERRNO_HOOK_ENTER;
+
+  int r;
+#ifdef SH_CONFIG_TRY_WITH_EXIT
+  if (0 == (r = sh_inst_hook_with_exit(self, target_addr, dlinfo, new_addr, orig_addr, orig_addr2))) return r;
+#endif
+  if (0 == (r = sh_inst_hook_without_exit(self, target_addr, dlinfo, new_addr, orig_addr, orig_addr2)))
+    return r;
+
+  // hook failed
+  if (NULL != orig_addr) *orig_addr = 0;
+  if (NULL != orig_addr2) *orig_addr2 = 0;
+  sh_enter_free(self->enter_addr);
+  return r;
+}
+
+int sh_inst_unhook(sh_inst_t *self, uintptr_t target_addr) {
+  int r;
+
+  // restore the instructions at the target address
+  SH_SIG_TRY(SIGSEGV, SIGBUS) {
+    r = memcmp((void *)target_addr, self->trampo, self->backup_len);
+  }
+  SH_SIG_CATCH() {
+    return SHADOWHOOK_ERRNO_UNHOOK_CMP_CRASH;
+  }
+  SH_SIG_EXIT
+  if (0 != r) return SHADOWHOOK_ERRNO_UNHOOK_TRAMPO_MISMATCH;
+  if (0 != (r = sh_util_write_inst(target_addr, self->backup, self->backup_len))) return r;
+  __atomic_thread_fence(__ATOMIC_SEQ_CST);
+
+  // free memory space for exit
+  if (0 != self->exit_addr)
+    if (0 != (r = sh_exit_free(self->exit_addr, (uint16_t)self->exit_type, (uint8_t *)(self->exit),
+                               sizeof(self->exit))))
+      return r;
+
+  // free memory space for enter
+  sh_enter_free(self->enter_addr);
+
+  SH_LOG_INFO("a64: unhook OK. target %" PRIxPTR, target_addr);
+  return 0;
+}
diff --git a/app/src/main/cpp/shadowhook/arch/arm64/sh_inst.h b/app/src/main/cpp/shadowhook/arch/arm64/sh_inst.h
new file mode 100644
index 0000000000000000000000000000000000000000..5988b68938dbe644b1460636c78fdd5539106027
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/arch/arm64/sh_inst.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#pragma once
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "xdl.h"
+
+typedef struct {
+  uint32_t trampo[4];   // align 16 // length == backup_len
+  uint8_t backup[16];   // align 16
+  uint32_t backup_len;  // == 4 or 16
+  uint32_t exit_type;
+  uintptr_t exit_addr;
+  uint32_t exit[4];
+  uintptr_t enter_addr;
+} sh_inst_t;
+
+int sh_inst_hook(sh_inst_t *self, uintptr_t target_addr, xdl_info_t *dlinfo, uintptr_t new_addr,
+                 uintptr_t *orig_addr, uintptr_t *orig_addr2);
+int sh_inst_unhook(sh_inst_t *self, uintptr_t target_addr);
diff --git a/app/src/main/cpp/shadowhook/common/bytesig.c b/app/src/main/cpp/shadowhook/common/bytesig.c
new file mode 100644
index 0000000000000000000000000000000000000000..8568279f4987369df0465c776eddecc6d9ddaf04
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/common/bytesig.c
@@ -0,0 +1,291 @@
+// Copyright (c) 2021-2023 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+// version 1.0.4
+
+#include "bytesig.h"
+
+#include <dlfcn.h>
+#include <errno.h>
+#include <pthread.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#define BYTESIG_DEBUG 0
+
+#if BYTESIG_DEBUG
+#include <android/log.h>
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
+#define BYTESIG_LOG(fmt, ...) __android_log_print(ANDROID_LOG_INFO, "bytesig_tag", fmt, ##__VA_ARGS__)
+#pragma clang diagnostic pop
+#else
+#define BYTESIG_LOG(fmt, ...)
+#endif
+
+typedef enum {
+  BYTESIG_STATUS_UNAVAILABLE = 0,
+  BYTESIG_STATUS_SIG32 = 1,  // use sigset_t
+  BYTESIG_STATUS_SIG64 = 2   // use sigset64_t
+} bytesig_status_t;
+static bytesig_status_t bytesig_status = BYTESIG_STATUS_UNAVAILABLE;
+
+extern __attribute((weak)) int sigfillset64(sigset64_t *);
+extern __attribute((weak)) int sigemptyset64(sigset64_t *);
+extern __attribute((weak)) int sigaddset64(sigset64_t *, int);
+extern __attribute((weak)) int sigismember64(const sigset64_t *, int);
+
+typedef int (*bytesig_sigaction64_t)(int, const struct sigaction64 *, struct sigaction64 *);
+typedef int (*bytesig_sigaction_t)(int, const struct sigaction *, struct sigaction *);
+typedef int (*bytesig_sigprocmask64_t)(int, const sigset64_t *, sigset64_t *);
+typedef int (*bytesig_sigprocmask_t)(int, const sigset_t *, sigset_t *);
+
+static void *bytesig_sigaction;    // point to libc's sigaction64() or libc's sigaction()
+static void *bytesig_sigprocmask;  // point to libc's sigprocmask() or libc's sigprocmask64()
+
+__attribute__((constructor)) static void bytesig_ctor(void) {
+  void *libc = dlopen("libc.so", RTLD_LOCAL);
+  if (__predict_false(NULL == libc)) return;
+
+  if (__predict_true(NULL != sigfillset64 && NULL != sigemptyset64 && NULL != sigaddset64 &&
+                     NULL != sigismember64)) {
+    if (__predict_true(NULL != (bytesig_sigaction = dlsym(libc, "sigaction64")) &&
+                       NULL != (bytesig_sigprocmask = dlsym(libc, "sigprocmask64")))) {
+      bytesig_status = BYTESIG_STATUS_SIG64;
+      goto end;
+    }
+  }
+
+  if (__predict_true(NULL != (bytesig_sigaction = dlsym(libc, "sigaction")) &&
+                     NULL != (bytesig_sigprocmask = dlsym(libc, "sigprocmask")))) {
+    bytesig_status = BYTESIG_STATUS_SIG32;
+  }
+
+end:
+  dlclose(libc);
+}
+
+#define BYTESIG_PROTECTED_THREADS_MAX 256
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+typedef struct {
+  pid_t tids[BYTESIG_PROTECTED_THREADS_MAX];
+  sigjmp_buf *jbufs[BYTESIG_PROTECTED_THREADS_MAX];
+  union {
+    struct sigaction64 prev_action64;
+    struct sigaction prev_action;
+  };
+} bytesig_signal_t;
+#pragma clang diagnostic pop
+
+// array index is signal number, corresponds to signals 1 to 31, except 9 and 19
+static bytesig_signal_t *bytesig_signal_array[__SIGRTMIN];
+
+static void bytesig_sigorset64(sigset64_t *dest, sigset64_t *left, sigset64_t *right) {
+  sigemptyset64(dest);
+  for (size_t i = 1; i < sizeof(sigset64_t) * CHAR_BIT; i++)
+    if (sigismember64(left, (int)i) == 1 || sigismember64(right, (int)i) == 1) sigaddset64(dest, (int)i);
+}
+
+static void bytesig_sigorset(sigset_t *dest, sigset_t *left, sigset_t *right) {
+  sigemptyset(dest);
+  for (size_t i = 1; i < sizeof(sigset_t) * CHAR_BIT; i++)
+    if (sigismember(left, (int)i) == 1 || sigismember(right, (int)i) == 1) sigaddset(dest, (int)i);
+}
+
+__attribute__((noinline)) static void bytesig_handler_internal(int signum, siginfo_t *siginfo,
+                                                               void *context) {
+  bytesig_signal_t *sig = bytesig_signal_array[signum];
+
+  // check protect info & do siglongjmp
+  pid_t tid = gettid();
+  if (__predict_false(0 == tid)) tid = (pid_t)syscall(SYS_gettid);
+  for (size_t i = 0; i < BYTESIG_PROTECTED_THREADS_MAX; i++) {
+    if (__predict_false(tid == __atomic_load_n(&(sig->tids[i]), __ATOMIC_RELAXED))) {
+      BYTESIG_LOG("siglongjmp signal %d (code %d) at %zu", signum, siginfo->si_code, i);
+
+      unsigned int ret_signum = (((unsigned int)signum & 0xFFU) << 16U);
+      unsigned int ret_code = 0U;
+      if (siginfo->si_code > 0)
+        ret_code = (((unsigned int)(siginfo->si_code) & 0xFFU) << 8U);
+      else if (siginfo->si_code < 0)
+        ret_code = (unsigned int)(-(siginfo->si_code)) & 0xFFU;
+      int ret_val = (int)(ret_signum | ret_code);
+
+      siglongjmp(*(__atomic_load_n(&(sig->jbufs[i]), __ATOMIC_RELAXED)), ret_val);
+    }
+  }
+
+#define SET_THREAD_SIGNAL_MASK(suffix)                                                       \
+  do {                                                                                       \
+    sigset##suffix##_t prev_mask;                                                            \
+    bytesig_sigorset##suffix(&prev_mask, &(((ucontext_t *)context)->uc_sigmask##suffix),     \
+                             &(sig->prev_action##suffix.sa_mask));                           \
+    if (0 == ((unsigned int)(sig->prev_action##suffix.sa_flags) & (unsigned int)SA_NODEFER)) \
+      sigaddset##suffix(&prev_mask, signum);                                                 \
+    sigaddset##suffix(&prev_mask, SIGPIPE);                                                  \
+    sigaddset##suffix(&prev_mask, SIGUSR1);                                                  \
+    sigaddset##suffix(&prev_mask, SIGQUIT);                                                  \
+    ((bytesig_sigprocmask##suffix##_t)bytesig_sigprocmask)(SIG_SETMASK, &prev_mask, NULL);   \
+  } while (0)
+
+  // set thread signal mask
+  if (BYTESIG_STATUS_SIG64 == bytesig_status)
+    SET_THREAD_SIGNAL_MASK(64);
+  else
+    SET_THREAD_SIGNAL_MASK();
+}
+
+// https://llvm.org/docs/CodeGenerator.html#tail-call-optimization
+// https://clang.llvm.org/docs/AttributeReference.html#disable-tail-calls
+//__attribute__((disable_tail_calls))
+static void bytesig_handler(int signum, siginfo_t *siginfo, void *context) {
+  bytesig_handler_internal(signum, siginfo, context);
+
+#define CALL_PREVIOUS_SIGNAL_HANDLER(suffix)                            \
+  do {                                                                  \
+    if (__predict_true(sig->prev_action##suffix.sa_flags & SA_SIGINFO)) \
+      sig->prev_action##suffix.sa_sigaction(signum, siginfo, context);  \
+    else if (SIG_DFL != sig->prev_action##suffix.sa_handler &&          \
+             SIG_IGN != sig->prev_action##suffix.sa_handler)            \
+      sig->prev_action##suffix.sa_handler(signum);                      \
+  } while (0)
+
+  // call previous signal handler
+  bytesig_signal_t *sig = bytesig_signal_array[signum];
+  if (BYTESIG_STATUS_SIG64 == bytesig_status)
+    CALL_PREVIOUS_SIGNAL_HANDLER(64);
+  else
+    CALL_PREVIOUS_SIGNAL_HANDLER();
+}
+
+int bytesig_init(int signum) {
+  if (__predict_false(signum <= 0 || signum >= __SIGRTMIN || signum == SIGKILL || signum == SIGSTOP))
+    return -1;
+  if (__predict_false(BYTESIG_STATUS_UNAVAILABLE == bytesig_status)) return -1;
+  if (__predict_false(NULL != bytesig_signal_array[signum])) return -1;
+
+  static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+  pthread_mutex_lock(&lock);
+  int ret = -1;
+  if (__predict_false(NULL != bytesig_signal_array[signum])) goto end;
+
+  bytesig_signal_t *sig = calloc(1, sizeof(bytesig_signal_t));
+  if (__predict_false(NULL == sig)) goto end;
+
+#define SA_EXPOSE_TAGBITS 0x00000800
+
+#define REGISTER_SIGNAL_HANDLER(suffix)                                                                     \
+  do {                                                                                                      \
+    struct sigaction##suffix act;                                                                           \
+    memset(&act, 0, sizeof(struct sigaction##suffix));                                                      \
+    sigfillset##suffix(&act.sa_mask);                                                                       \
+    act.sa_sigaction = bytesig_handler;                                                                     \
+    act.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART | SA_EXPOSE_TAGBITS;                                \
+    if (__predict_false(                                                                                    \
+            0 !=                                                                                            \
+            ((bytesig_sigaction##suffix##_t)bytesig_sigaction)(signum, &act, &sig->prev_action##suffix))) { \
+      free(sig);                                                                                            \
+      goto end;                                                                                             \
+    }                                                                                                       \
+  } while (0)
+
+  // register the signal handler, we start off with all signals blocked
+  if (BYTESIG_STATUS_SIG64 == bytesig_status)
+    REGISTER_SIGNAL_HANDLER(64);
+  else
+    REGISTER_SIGNAL_HANDLER();
+
+  bytesig_signal_array[signum] = sig;
+  ret = 0;  // OK
+
+end:
+  pthread_mutex_unlock(&lock);
+  return ret;
+}
+
+void bytesig_protect(pid_t tid, sigjmp_buf *jbuf, const int signums[], size_t signums_cnt) {
+  for (size_t i = 0; i < signums_cnt; i++) {
+    int signum = signums[i];
+    if (__predict_false(signum <= 0 || signum >= __SIGRTMIN || signum == SIGKILL || signum == SIGSTOP))
+      continue;
+
+    bytesig_signal_t *sig = bytesig_signal_array[signum];
+    if (__predict_false(NULL == sig)) continue;
+
+    // check repeated thread
+    bool repeated = false;
+    for (size_t j = 0; j < BYTESIG_PROTECTED_THREADS_MAX; j++) {
+      if (__predict_false(tid == sig->tids[j])) {
+        repeated = true;
+        break;
+      }
+    }
+    if (__predict_false(repeated)) continue;
+
+    // save thread-ID and jump buffer
+    size_t j = 0;
+    while (1) {
+      if (0 == sig->tids[j]) {
+        pid_t expected = 0;
+        if (__atomic_compare_exchange_n(&sig->tids[j], &expected, tid, false, __ATOMIC_ACQUIRE,
+                                        __ATOMIC_RELAXED)) {
+          sig->jbufs[j] = jbuf;
+          BYTESIG_LOG("protect_start signal %d at %zu", signum, j);
+          break;  // finished
+        }
+      }
+
+      j++;
+      if (__predict_false(BYTESIG_PROTECTED_THREADS_MAX == j)) j = 0;
+    }
+  }
+}
+
+void bytesig_unprotect(pid_t tid, const int signums[], size_t signums_cnt) {
+  for (size_t i = 0; i < signums_cnt; i++) {
+    int signum = signums[i];
+    if (__predict_false(signum <= 0 || signum >= __SIGRTMIN || signum == SIGKILL || signum == SIGSTOP))
+      continue;
+
+    bytesig_signal_t *sig = bytesig_signal_array[signum];
+    if (__predict_false(NULL == sig)) continue;
+
+    // free thread-ID and jump buffer
+    for (size_t j = 0; j < BYTESIG_PROTECTED_THREADS_MAX; j++) {
+      if (tid == sig->tids[j]) {
+        sig->jbufs[j] = NULL;
+        __atomic_store_n(&sig->tids[j], 0, __ATOMIC_RELEASE);
+        BYTESIG_LOG("protect_end signal %d at %zu", signum, j);
+        break;  // finished
+      }
+    }
+  }
+}
diff --git a/app/src/main/cpp/shadowhook/common/bytesig.h b/app/src/main/cpp/shadowhook/common/bytesig.h
new file mode 100644
index 0000000000000000000000000000000000000000..a7de52682ef43a49b3900c0c53ba6902682b21e6
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/common/bytesig.h
@@ -0,0 +1,157 @@
+// Copyright (c) 2021-2023 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+// version 1.0.4
+
+/*
+ * #include "bytesig.h"
+ *
+ * void init_once(void)
+ * {
+ *     bytesig_init(SIGSEGV);
+ *     bytesig_init(SIGBUS);
+ *     bytesig_init(SIGILL);
+ *     bytesig_init(SIGABRT);
+ *     // ......
+ * }
+ *
+ * void unstable_func(void)
+ * {
+ *     int *p = NULL;
+ *
+ *     //
+ *     // usage 1
+ *     //
+ *     BYTESIG_TRY(SIGSEGV, SIGBUS)
+ *     {
+ *         *p = 1;
+ *     }
+ *     BYTESIG_CATCH(signum, code)
+ *     {
+ *         LOG("signum %d (code %d)", signum, code);
+ *     }
+ *     BYTESIG_EXIT
+ *
+ *     //
+ *     // usage 2
+ *     //
+ *     BYTESIG_TRY(SIGSEGV, SIGBUS)
+ *     {
+ *         *p = 2;
+ *     }
+ *     BYTESIG_CATCH(signum)
+ *     {
+ *         LOG("signum %d", signum);
+ *     }
+ *     BYTESIG_EXIT
+ *
+ *     //
+ *     // usage 3
+ *     //
+ *     BYTESIG_TRY(SIGILL)
+ *     {
+ *         func_maybe_illed();
+ *     }
+ *     BYTESIG_CATCH()
+ *     {
+ *         do_something();
+ *     }
+ *     BYTESIG_EXIT
+ *
+ *     //
+ *     // usage 4
+ *     //
+ *     BYTESIG_TRY(SIGABRT)
+ *     {
+ *         func_maybe_aborted();
+ *     }
+ *     BYTESIG_EXIT
+ * }
+ */
+
+#ifndef BYTEDANCE_BYTESIG
+#define BYTEDANCE_BYTESIG
+
+#include <setjmp.h>
+#include <signal.h>
+#include <stddef.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#define BYTESIG_TRY(...)                                                                                   \
+  do {                                                                                                     \
+    pid_t _bytesig_tid_ = gettid();                                                                        \
+    if (0 == _bytesig_tid_) _bytesig_tid_ = (pid_t)syscall(SYS_gettid);                                    \
+    sigjmp_buf _bytesig_jbuf_;                                                                             \
+    int _bytesig_sigs_[] = {__VA_ARGS__};                                                                  \
+    bytesig_protect(_bytesig_tid_, &_bytesig_jbuf_, _bytesig_sigs_, sizeof(_bytesig_sigs_) / sizeof(int)); \
+    int _bytesig_protected_ = 1;                                                                           \
+    int _bytesig_ex_ = sigsetjmp(_bytesig_jbuf_, 1);                                                       \
+    if (0 == _bytesig_ex_) {
+#define BYTESIG_CATCH_2(signum_, code_)                                                     \
+  }                                                                                         \
+  else {                                                                                    \
+    bytesig_unprotect(_bytesig_tid_, _bytesig_sigs_, sizeof(_bytesig_sigs_) / sizeof(int)); \
+    _bytesig_protected_ = 0;                                                                \
+    int signum_ = (int)(((unsigned int)_bytesig_ex_ & 0xFF0000U) >> 16U);                   \
+    int code_ = 0;                                                                          \
+    if (((unsigned int)_bytesig_ex_ & 0xFF00U) > 0)                                         \
+      code_ = (int)(((unsigned int)_bytesig_ex_ & 0xFF00U) >> 8U);                          \
+    else if (((unsigned int)_bytesig_ex_ & 0xFFU) > 0)                                      \
+      code_ = -((int)((unsigned int)_bytesig_ex_ & 0xFFU));                                 \
+    (void)signum_;                                                                          \
+    (void)code_;
+
+#define BYTESIG_CATCH_1(signum_) BYTESIG_CATCH_2(signum_, _bytesig_code_)
+#define BYTESIG_CATCH_0()        BYTESIG_CATCH_1(_bytesig_signum_)
+
+#define FUNC_CHOOSER(_f1, _f2, _f3, ...)     _f3
+#define FUNC_RECOMPOSER(argsWithParentheses) FUNC_CHOOSER argsWithParentheses
+#define CHOOSE_FROM_ARG_COUNT(...)           FUNC_RECOMPOSER((__VA_ARGS__, BYTESIG_CATCH_2, BYTESIG_CATCH_1, ))
+#define NO_ARG_EXPANDER()                    , , BYTESIG_CATCH_0
+#define MACRO_CHOOSER(...)                   CHOOSE_FROM_ARG_COUNT(NO_ARG_EXPANDER __VA_ARGS__())
+
+#define BYTESIG_CATCH(...) MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)
+
+#define BYTESIG_EXIT                                                                        \
+  }                                                                                         \
+  if (1 == _bytesig_protected_)                                                             \
+    bytesig_unprotect(_bytesig_tid_, _bytesig_sigs_, sizeof(_bytesig_sigs_) / sizeof(int)); \
+  }                                                                                         \
+  while (0)                                                                                 \
+    ;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int bytesig_init(int signum);
+
+void bytesig_protect(pid_t tid, sigjmp_buf *jbuf, const int signums[], size_t signums_cnt);
+void bytesig_unprotect(pid_t tid, const int signums[], size_t signums_cnt);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/app/src/main/cpp/shadowhook/common/sh_config.h b/app/src/main/cpp/shadowhook/common/sh_config.h
new file mode 100644
index 0000000000000000000000000000000000000000..464dea95519c0c77da1567cba0718518726bc155
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/common/sh_config.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#pragma once
+
+// Global debugging.
+//
+// Note that in some cases these logs themselves can cause a crash.
+//
+//#define SH_CONFIG_DEBUG
+
+// Operation record of hook and unhook.
+//
+// Disabling it can reduce hook/unhook latency, memory footprint and file size.
+//
+#define SH_CONFIG_OPERATION_RECORDS
+
+// Crash signal protection.
+//
+// Do not disable it in a production environment.
+//
+#define SH_CONFIG_SIG_PROT
+
+// When hooking, try to find an exit for the relative jump for the dynamic library.
+//
+// Do not disable it in a production environment.
+//
+#define SH_CONFIG_TRY_WITH_EXIT
+
+// When hooking the function of the thumb instruction, if the function is too short,
+// try to use the gap aligned at the end of the function.
+//
+// Do not disable it in a production environment.
+//
+#define SH_CONFIG_DETECT_THUMB_TAIL_ALIGNED
diff --git a/app/src/main/cpp/shadowhook/common/sh_errno.c b/app/src/main/cpp/shadowhook/common/sh_errno.c
new file mode 100644
index 0000000000000000000000000000000000000000..e55891e40bd73e7fd3d9581eeffe7b48044f1bbd
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/common/sh_errno.c
@@ -0,0 +1,107 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#include "sh_errno.h"
+
+#include <pthread.h>
+#include <stdbool.h>
+
+#include "shadowhook.h"
+
+static int sh_errno_global = SHADOWHOOK_ERRNO_UNINIT;
+static pthread_key_t sh_errno_tls_key;
+
+int sh_errno_init(void) {
+  if (__predict_false(0 != pthread_key_create(&sh_errno_tls_key, NULL))) {
+    sh_errno_global = SHADOWHOOK_ERRNO_INIT_ERRNO;
+    return -1;
+  }
+  sh_errno_global = SHADOWHOOK_ERRNO_OK;
+  return 0;
+}
+
+void sh_errno_reset(void) {
+  sh_errno_set(0);
+}
+
+void sh_errno_set(int error_number) {
+  if (sh_errno_global == SHADOWHOOK_ERRNO_UNINIT || sh_errno_global == SHADOWHOOK_ERRNO_INIT_ERRNO) return;
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast"
+  pthread_setspecific(sh_errno_tls_key, (void *)error_number);
+#pragma clang diagnostic pop
+}
+
+int sh_errno_get(void) {
+  if (sh_errno_global == SHADOWHOOK_ERRNO_UNINIT || sh_errno_global == SHADOWHOOK_ERRNO_INIT_ERRNO)
+    return sh_errno_global;
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wvoid-pointer-to-int-cast"
+  return (int)(pthread_getspecific(sh_errno_tls_key));
+#pragma clang diagnostic pop
+}
+
+const char *sh_errno_to_errmsg(int error_number) {
+  static const char *msg[] = {/* 0  */ "OK",
+                              /* 1  */ "Pending task",
+                              /* 2  */ "Not initialized",
+                              /* 3  */ "Invalid argument",
+                              /* 4  */ "Out of memory",
+                              /* 5  */ "MProtect failed",
+                              /* 6  */ "Write to arbitrary address crashed",
+                              /* 7  */ "Init errno mod failed",
+                              /* 8  */ "Init bytesig SIGSEGV mod failed",
+                              /* 9  */ "Init bytesig SIGBUS mod failed",
+                              /* 10 */ "Init enter mod failed",
+                              /* 11 */ "Init safe mod failed",
+                              /* 12 */ "Init linker mod failed",
+                              /* 13 */ "Init hub mod failed",
+                              /* 14 */ "Create hub failed",
+                              /* 15 */ "Monitor dlopen failed",
+                              /* 16 */ "Create monitor thread failed",
+                              /* 17 */ "Open ELF crashed",
+                              /* 18 */ "Find symbol in ELF failed",
+                              /* 19 */ "Find symbol in ELF crashed",
+                              /* 20 */ "Duplicate hook",
+                              /* 21 */ "Dladdr crashed",
+                              /* 22 */ "Find dlinfo failed",
+                              /* 23 */ "Symbol size too small",
+                              /* 24 */ "Alloc enter failed",
+                              /* 25 */ "Instruction rewrite crashed",
+                              /* 26 */ "Instruction rewrite failed",
+                              /* 27 */ "Switch not found",
+                              /* 28 */ "Verify original instruction crashed",
+                              /* 29 */ "Verify original instruction failed",
+                              /* 30 */ "Exit instruction mismatch",
+                              /* 31 */ "Free exit crashed",
+                              /* 32 */ "Unhook on an error status task",
+                              /* 33 */ "Unhook on an unfinished task",
+                              /* 34 */ "ELF with an unsupported architecture",
+                              /* 35 */ "Linker with an unsupported architecture"};
+
+  if (error_number < 0 || error_number >= (int)(sizeof(msg) / sizeof(msg[0]))) return "Unknown error number";
+
+  return msg[error_number];
+}
diff --git a/app/src/main/cpp/shadowhook/common/sh_errno.h b/app/src/main/cpp/shadowhook/common/sh_errno.h
new file mode 100644
index 0000000000000000000000000000000000000000..10f017b988e1a0d9251d02e4bfe3c9c6a8023543
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/common/sh_errno.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#pragma once
+#include "sh_log.h"
+
+#define SH_ERRNO_SET_RET_ERRNUM(errnum) SH_ERRNO_SET_RET((errnum), (errnum))
+#define SH_ERRNO_SET_RET_FAIL(errnum)   SH_ERRNO_SET_RET((errnum), -1)
+#define SH_ERRNO_SET_RET_NULL(errnum)   SH_ERRNO_SET_RET((errnum), NULL)
+#define SH_ERRNO_SET_RET(errnum, ret) \
+  do {                                \
+    sh_errno_set((errnum));           \
+    return (ret);                     \
+  } while (0)
+
+int sh_errno_init(void);
+void sh_errno_reset(void);
+void sh_errno_set(int error_number);
+int sh_errno_get(void);
+const char *sh_errno_to_errmsg(int error_number);
diff --git a/app/src/main/cpp/shadowhook/common/sh_log.c b/app/src/main/cpp/shadowhook/common/sh_log.c
new file mode 100644
index 0000000000000000000000000000000000000000..26327233341e1df448f9a1c5f4a720a64ab01479
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/common/sh_log.c
@@ -0,0 +1,53 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#include "sh_log.h"
+
+#include <android/log.h>
+#include <stdbool.h>
+
+#include "sh_config.h"
+
+android_LogPriority sh_log_priority =
+#ifdef SH_CONFIG_DEBUG
+    ANDROID_LOG_INFO
+#else
+    ANDROID_LOG_SILENT
+#endif
+    ;
+
+bool sh_log_get_debuggable(void) {
+  return sh_log_priority <= ANDROID_LOG_INFO;
+}
+
+void sh_log_set_debuggable(bool debuggable) {
+#ifdef SH_CONFIG_DEBUG
+  (void)debuggable;
+  sh_log_priority = ANDROID_LOG_INFO;
+#else
+  if (__predict_false(debuggable))
+    sh_log_priority = ANDROID_LOG_INFO;
+  else
+    sh_log_priority = ANDROID_LOG_SILENT;
+#endif
+}
diff --git a/app/src/main/cpp/shadowhook/common/sh_log.h b/app/src/main/cpp/shadowhook/common/sh_log.h
new file mode 100644
index 0000000000000000000000000000000000000000..fff786f6cddf48bb4189e670e730f5dce7f5d4cf
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/common/sh_log.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#pragma once
+#include <android/log.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+extern android_LogPriority sh_log_priority;
+
+#define SH_LOG_TAG "shadowhook_tag"
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
+
+// Notice:
+// We don't use ANDROID_LOG_DEBUG, because some Android devices will filter out ANDROID_LOG_DEBUG.
+#ifdef SH_CONFIG_DEBUG
+#define SH_LOG_DEBUG(fmt, ...)                                               \
+  do {                                                                       \
+    if (sh_log_priority <= ANDROID_LOG_INFO)                                 \
+      __android_log_print(ANDROID_LOG_INFO, SH_LOG_TAG, fmt, ##__VA_ARGS__); \
+  } while (0)
+#else
+#define SH_LOG_DEBUG(fmt, ...)
+#endif
+
+#define SH_LOG_INFO(fmt, ...)                                                \
+  do {                                                                       \
+    if (__predict_false(sh_log_priority <= ANDROID_LOG_INFO))                \
+      __android_log_print(ANDROID_LOG_INFO, SH_LOG_TAG, fmt, ##__VA_ARGS__); \
+  } while (0)
+#define SH_LOG_WARN(fmt, ...)                                                \
+  do {                                                                       \
+    if (__predict_false(sh_log_priority <= ANDROID_LOG_WARN))                \
+      __android_log_print(ANDROID_LOG_WARN, SH_LOG_TAG, fmt, ##__VA_ARGS__); \
+  } while (0)
+#define SH_LOG_ERROR(fmt, ...)                                                \
+  do {                                                                        \
+    if (__predict_false(sh_log_priority <= ANDROID_LOG_ERROR))                \
+      __android_log_print(ANDROID_LOG_ERROR, SH_LOG_TAG, fmt, ##__VA_ARGS__); \
+  } while (0)
+#define SH_LOG_ALWAYS_SHOW(fmt, ...) __android_log_print(ANDROID_LOG_WARN, SH_LOG_TAG, fmt, ##__VA_ARGS__)
+
+#pragma clang diagnostic pop
+
+bool sh_log_get_debuggable(void);
+void sh_log_set_debuggable(bool debuggable);
diff --git a/app/src/main/cpp/shadowhook/common/sh_sig.h b/app/src/main/cpp/shadowhook/common/sh_sig.h
new file mode 100644
index 0000000000000000000000000000000000000000..5b926ae15913c04e699fe4fbf1d6df72654ce6c5
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/common/sh_sig.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#pragma once
+
+#include "sh_config.h"
+
+#ifdef SH_CONFIG_SIG_PROT
+
+#include "bytesig.h"
+#define SH_SIG_TRY   BYTESIG_TRY
+#define SH_SIG_CATCH BYTESIG_CATCH
+#define SH_SIG_EXIT  BYTESIG_EXIT
+
+#else
+
+#define SH_SIG_TRY(...) \
+  do {                  \
+    if (0 == 0) {
+#define SH_SIG_CATCH(...) \
+  }                       \
+  else {
+#define SH_SIG_EXIT \
+  }                 \
+  }                 \
+  while (0)         \
+    ;
+
+#endif
diff --git a/app/src/main/cpp/shadowhook/common/sh_trampo.c b/app/src/main/cpp/shadowhook/common/sh_trampo.c
new file mode 100644
index 0000000000000000000000000000000000000000..894273cdf5cdd8c8f3aa5f435dac456459e00c6a
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/common/sh_trampo.c
@@ -0,0 +1,172 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#include "sh_trampo.h"
+
+#include <pthread.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+#include <sys/time.h>
+
+#include "queue.h"
+#include "sh_util.h"
+
+#define SH_TRAMPO_PAGE_SZ 4096
+#define SH_TRAMPO_ALIGN   4
+
+void sh_trampo_init_mgr(sh_trampo_mgr_t *mem_mgr, const char *page_name, size_t trampo_size,
+                        time_t delay_sec) {
+  SLIST_INIT(&mem_mgr->pages);
+  pthread_mutex_init(&mem_mgr->pages_lock, NULL);
+  mem_mgr->page_name = page_name;
+  mem_mgr->trampo_size = SH_UTIL_ALIGN_END(trampo_size, SH_TRAMPO_ALIGN);
+  mem_mgr->delay_sec = delay_sec;
+}
+
+uintptr_t sh_trampo_alloc(sh_trampo_mgr_t *mem_mgr, uintptr_t hint, uintptr_t range_low,
+                          uintptr_t range_high) {
+  uintptr_t mem = 0;
+  uintptr_t new_ptr;
+  uintptr_t new_ptr_prctl = (uintptr_t)MAP_FAILED;
+  size_t count = SH_TRAMPO_PAGE_SZ / mem_mgr->trampo_size;
+
+  if (range_low > hint) range_low = hint;
+  if (range_high > UINTPTR_MAX - hint) range_high = UINTPTR_MAX - hint;
+
+  struct timeval now;
+  if (mem_mgr->delay_sec > 0) gettimeofday(&now, NULL);
+
+  pthread_mutex_lock(&mem_mgr->pages_lock);
+
+  // try to find an unused mem
+  sh_trampo_page_t *page;
+  SLIST_FOREACH(page, &mem_mgr->pages, link) {
+    // check hit range
+    uintptr_t page_trampo_start = page->ptr;
+    uintptr_t page_trampo_end = page->ptr + SH_TRAMPO_PAGE_SZ - mem_mgr->trampo_size;
+    if (hint > 0 && ((page_trampo_end < hint - range_low) || (hint + range_high < page_trampo_start)))
+      continue;
+
+    for (uintptr_t i = 0; i < count; i++) {
+      size_t flags_idx = i / 32;
+      uint32_t mask = (uint32_t)1 << (i % 32);
+      if (0 == (page->flags[flags_idx] & mask))  // check flag
+      {
+        // check timestamp
+        if (mem_mgr->delay_sec > 0 &&
+            (now.tv_sec <= page->timestamps[i] || now.tv_sec - page->timestamps[i] <= mem_mgr->delay_sec))
+          continue;
+
+        // check hit range
+        uintptr_t cur = page->ptr + (mem_mgr->trampo_size * i);
+        if (hint > 0 && ((cur < hint - range_low) || (hint + range_high < cur))) continue;
+
+        // OK
+        page->flags[flags_idx] |= mask;
+        mem = cur;
+        memset((void *)mem, 0, mem_mgr->trampo_size);
+        goto end;
+      }
+    }
+  }
+
+  // alloc a new mem page
+  new_ptr = (uintptr_t)(mmap(hint > 0 ? (void *)(hint - range_low) : NULL, SH_TRAMPO_PAGE_SZ,
+                             PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
+  if ((uintptr_t)MAP_FAILED == new_ptr) goto err;
+  new_ptr_prctl = new_ptr;
+
+  // check hit range
+  if (hint > 0 && ((hint - range_low >= new_ptr + SH_TRAMPO_PAGE_SZ - mem_mgr->trampo_size) ||
+                   (hint + range_high < new_ptr)))
+    goto err;
+
+  // create a new trampo-page info
+  if (NULL == (page = calloc(1, sizeof(sh_trampo_page_t)))) goto err;
+  memset((void *)new_ptr, 0, SH_TRAMPO_PAGE_SZ);
+  page->ptr = new_ptr;
+  new_ptr = (uintptr_t)MAP_FAILED;
+  if (NULL == (page->flags = calloc(1, SH_UTIL_ALIGN_END(count, 32) / 8))) goto err;
+  page->timestamps = NULL;
+  if (mem_mgr->delay_sec > 0) {
+    if (NULL == (page->timestamps = calloc(1, count * sizeof(time_t)))) goto err;
+  }
+  SLIST_INSERT_HEAD(&mem_mgr->pages, page, link);
+
+  // alloc mem from the new mem page
+  for (uintptr_t i = 0; i < count; i++) {
+    size_t flags_idx = i / 32;
+    uint32_t mask = (uint32_t)1 << (i % 32);
+
+    // check hit range
+    uintptr_t find = page->ptr + (mem_mgr->trampo_size * i);
+    if (hint > 0 && ((find < hint - range_low) || (hint + range_high < find))) continue;
+
+    // OK
+    page->flags[flags_idx] |= mask;
+    mem = find;
+    break;
+  }
+  if (0 == mem) abort();
+
+end:
+  pthread_mutex_unlock(&mem_mgr->pages_lock);
+  if ((uintptr_t)MAP_FAILED != new_ptr_prctl)
+    prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, new_ptr_prctl, SH_TRAMPO_PAGE_SZ, mem_mgr->page_name);
+  return mem;
+
+err:
+  pthread_mutex_unlock(&mem_mgr->pages_lock);
+  if (NULL != page) {
+    if (0 != page->ptr) munmap((void *)page->ptr, SH_TRAMPO_PAGE_SZ);
+    if (NULL != page->flags) free(page->flags);
+    if (NULL != page->timestamps) free(page->timestamps);
+    free(page);
+  }
+  if ((uintptr_t)MAP_FAILED != new_ptr) munmap((void *)new_ptr, SH_TRAMPO_PAGE_SZ);
+  return 0;
+}
+
+void sh_trampo_free(sh_trampo_mgr_t *mem_mgr, uintptr_t mem) {
+  struct timeval now;
+  if (mem_mgr->delay_sec > 0) gettimeofday(&now, NULL);
+
+  pthread_mutex_lock(&mem_mgr->pages_lock);
+
+  sh_trampo_page_t *page;
+  SLIST_FOREACH(page, &mem_mgr->pages, link) {
+    if (page->ptr <= mem && mem < page->ptr + SH_TRAMPO_PAGE_SZ) {
+      uintptr_t i = (mem - page->ptr) / mem_mgr->trampo_size;
+      size_t flags_idx = i / 32;
+      uint32_t mask = (uint32_t)1 << (i % 32);
+      if (mem_mgr->delay_sec > 0) page->timestamps[i] = now.tv_sec;
+      page->flags[flags_idx] &= ~mask;
+      break;
+    }
+  }
+
+  pthread_mutex_unlock(&mem_mgr->pages_lock);
+}
diff --git a/app/src/main/cpp/shadowhook/common/sh_trampo.h b/app/src/main/cpp/shadowhook/common/sh_trampo.h
new file mode 100644
index 0000000000000000000000000000000000000000..0a20964f4a12d3a9ae97186dee90d084155e0903
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/common/sh_trampo.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#pragma once
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/time.h>
+
+#include "queue.h"
+
+typedef struct sh_trampo_page {
+  uintptr_t ptr;
+  uint32_t *flags;
+  time_t *timestamps;
+  SLIST_ENTRY(sh_trampo_page, ) link;
+} sh_trampo_page_t;
+typedef SLIST_HEAD(sh_trampo_page_list, sh_trampo_page, ) sh_trampo_page_list_t;
+
+typedef struct sh_trampo_mgr {
+  sh_trampo_page_list_t pages;
+  pthread_mutex_t pages_lock;
+  const char *page_name;
+  size_t trampo_size;
+  time_t delay_sec;
+} sh_trampo_mgr_t;
+
+void sh_trampo_init_mgr(sh_trampo_mgr_t *mem_mgr, const char *page_name, size_t trampo_size,
+                        time_t delay_sec);
+
+uintptr_t sh_trampo_alloc(sh_trampo_mgr_t *mem_mgr, uintptr_t hint, uintptr_t low_offset,
+                          uintptr_t high_offset);
+void sh_trampo_free(sh_trampo_mgr_t *mem_mgr, uintptr_t mem);
diff --git a/app/src/main/cpp/shadowhook/common/sh_util.c b/app/src/main/cpp/shadowhook/common/sh_util.c
new file mode 100644
index 0000000000000000000000000000000000000000..b8604c8bad609e314a8b3419d78b8afd6fee09ff
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/common/sh_util.c
@@ -0,0 +1,538 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#include "sh_util.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "sh_log.h"
+#include "sh_sig.h"
+#include "shadowhook.h"
+
+int sh_util_mprotect(uintptr_t addr, size_t len, int prot) {
+  uintptr_t start = SH_UTIL_PAGE_START(addr);
+  uintptr_t end = SH_UTIL_PAGE_END(addr + len - 1);
+
+  return mprotect((void *)start, end - start, prot);
+}
+
+void sh_util_clear_cache(uintptr_t addr, size_t len) {
+  __builtin___clear_cache((char *)addr, (char *)(addr + len));
+}
+
+bool sh_util_is_thumb32(uintptr_t target_addr) {
+  uint16_t opcode = *((uint16_t *)target_addr);
+  int tmp = opcode >> 11u;
+  return (tmp == 0x1d) || (tmp == 0x1e) || (tmp == 0x1f);
+}
+
+static uint32_t sh_util_ror(uint32_t val, uint32_t n, uint32_t shift) {
+  uint32_t m = shift % n;
+  return (val >> m) | (val << (n - m));
+}
+
+uint32_t sh_util_arm_expand_imm(uint32_t opcode) {
+  uint32_t imm = SH_UTIL_GET_BITS_32(opcode, 7, 0);
+  uint32_t amt = 2 * SH_UTIL_GET_BITS_32(opcode, 11, 8);
+
+  return amt == 0 ? imm : sh_util_ror(imm, 32, amt);
+}
+
+int sh_util_write_inst(uintptr_t target_addr, void *inst, size_t inst_len) {
+  if (0 != sh_util_mprotect(target_addr, inst_len, PROT_READ | PROT_WRITE | PROT_EXEC))
+    return SHADOWHOOK_ERRNO_MPROT;
+
+  SH_SIG_TRY(SIGSEGV, SIGBUS) {
+    if ((4 == inst_len) && (0 == target_addr % 4))
+      __atomic_store_n((uint32_t *)target_addr, *((uint32_t *)inst), __ATOMIC_SEQ_CST);
+    else if ((8 == inst_len) && (0 == target_addr % 8))
+      __atomic_store_n((uint64_t *)target_addr, *((uint64_t *)inst), __ATOMIC_SEQ_CST);
+#ifdef __LP64__
+    else if ((16 == inst_len) && (0 == target_addr % 16))
+      __atomic_store_n((__int128 *)target_addr, *((__int128 *)inst), __ATOMIC_SEQ_CST);
+#endif
+    else
+      memcpy((void *)target_addr, inst, inst_len);
+
+    sh_util_clear_cache(target_addr, inst_len);
+  }
+  SH_SIG_CATCH() {
+    return SHADOWHOOK_ERRNO_WRITE_CRASH;
+  }
+  SH_SIG_EXIT
+
+  return 0;  // OK
+}
+
+static bool sh_util_starts_with(const char *str, const char *start) {
+  while (*str && *str == *start) {
+    str++;
+    start++;
+  }
+
+  return '\0' == *start;
+}
+
+static int sh_util_get_api_level_from_build_prop(void) {
+  char buf[128];
+  int api_level = -1;
+
+  FILE *fp = fopen("/system/build.prop", "r");
+  if (__predict_false(NULL == fp)) goto end;
+
+  while (fgets(buf, sizeof(buf), fp)) {
+    if (__predict_false(sh_util_starts_with(buf, "ro.build.version.sdk="))) {
+      api_level = atoi(buf + 21);
+      break;
+    }
+  }
+  fclose(fp);
+
+end:
+  return (api_level > 0) ? api_level : -1;
+}
+
+int sh_util_get_api_level(void) {
+  static int xdl_util_api_level = -1;
+
+  if (__predict_false(xdl_util_api_level < 0)) {
+    int api_level = android_get_device_api_level();
+    if (__predict_false(api_level < 0))
+      api_level = sh_util_get_api_level_from_build_prop();  // compatible with unusual models
+    if (__predict_false(api_level < __ANDROID_API_J__)) api_level = __ANDROID_API_J__;
+
+    __atomic_store_n(&xdl_util_api_level, api_level, __ATOMIC_SEQ_CST);
+  }
+
+  return xdl_util_api_level;
+}
+
+int sh_util_write(int fd, const char *buf, size_t buf_len) {
+  if (fd < 0) return -1;
+
+  const char *ptr = buf;
+  size_t nleft = buf_len;
+
+  while (nleft > 0) {
+    errno = 0;
+    ssize_t nwritten = write(fd, ptr, nleft);
+    if (nwritten <= 0) {
+      if (nwritten < 0 && errno == EINTR)
+        nwritten = 0;  // call write() again
+      else
+        return -1;  // error
+    }
+    nleft -= (size_t)nwritten;
+    ptr += nwritten;
+  }
+
+  return 0;
+}
+
+/* Nonzero if YEAR is a leap year (every 4 years,
+   except every 100th isn't, and every 400th is).  */
+#define SH_UTIL_ISLEAP(year) ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
+
+#define SH_UTIL_SECS_PER_HOUR        (60 * 60)
+#define SH_UTIL_SECS_PER_DAY         (SH_UTIL_SECS_PER_HOUR * 24)
+#define SH_UTIL_DIV(a, b)            ((a) / (b) - ((a) % (b) < 0))
+#define SH_UTIL_LEAPS_THRU_END_OF(y) (SH_UTIL_DIV(y, 4) - SH_UTIL_DIV(y, 100) + SH_UTIL_DIV(y, 400))
+
+static const unsigned short int sh_util_mon_yday[2][13] = {
+    /* Normal years.  */
+    {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
+    /* Leap years.  */
+    {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}};
+
+/* Compute the `struct tm' representation of *T,
+   offset GMTOFF seconds east of UTC,
+   and store year, yday, mon, mday, wday, hour, min, sec into *RESULT.
+   Return RESULT if successful.  */
+struct tm *sh_util_localtime_r(const time_t *timep, long gmtoff, struct tm *result) {
+  time_t days, rem, y;
+  const unsigned short int *ip;
+
+  if (NULL == result) return NULL;
+
+  result->tm_gmtoff = gmtoff;
+
+  days = ((*timep) / SH_UTIL_SECS_PER_DAY);
+  rem = ((*timep) % SH_UTIL_SECS_PER_DAY);
+  rem += gmtoff;
+  while (rem < 0) {
+    rem += SH_UTIL_SECS_PER_DAY;
+    --days;
+  }
+  while (rem >= SH_UTIL_SECS_PER_DAY) {
+    rem -= SH_UTIL_SECS_PER_DAY;
+    ++days;
+  }
+  result->tm_hour = (int)(rem / SH_UTIL_SECS_PER_HOUR);
+  rem %= SH_UTIL_SECS_PER_HOUR;
+  result->tm_min = (int)(rem / 60L);
+  result->tm_sec = (int)(rem % 60L);
+  /* January 1, 1970 was a Thursday.  */
+  result->tm_wday = (int)(4 + days) % 7;
+  if (result->tm_wday < 0) result->tm_wday += 7;
+  y = 1970;
+
+  while (days < 0 || days >= (SH_UTIL_ISLEAP(y) ? 366 : 365)) {
+    /* Guess a corrected year, assuming 365 days per year.  */
+    time_t yg = y + days / 365 - (days % 365 < 0);
+
+    /* Adjust DAYS and Y to match the guessed year.  */
+    days -= ((yg - y) * 365 + SH_UTIL_LEAPS_THRU_END_OF(yg - 1) - SH_UTIL_LEAPS_THRU_END_OF(y - 1));
+
+    y = yg;
+  }
+  result->tm_year = (int)(y - 1900);
+  if (result->tm_year != y - 1900) {
+    /* The year cannot be represented due to overflow.  */
+    errno = EOVERFLOW;
+    return NULL;
+  }
+  result->tm_yday = (int)days;
+  ip = sh_util_mon_yday[SH_UTIL_ISLEAP(y)];
+  for (y = 11; days < (long int)ip[y]; --y) continue;
+  days -= ip[y];
+  result->tm_mon = (int)y;
+  result->tm_mday = (int)(days + 1);
+  return result;
+}
+
+static unsigned sh_util_parse_decimal(const char *format, int *ppos) {
+  const char *p = format + *ppos;
+  unsigned result = 0;
+  for (;;) {
+    int ch = *p;
+    unsigned d = (unsigned)(ch - '0');
+    if (d >= 10U) {
+      break;
+    }
+    result = result * 10 + d;
+    p++;
+  }
+  *ppos = (int)(p - format);
+  return result;
+}
+
+static void sh_util_format_unsigned(char *buf, size_t buf_size, uint64_t value, int base, int caps) {
+  char *p = buf;
+  char *end = buf + buf_size - 1;
+
+  // Generate digit string in reverse order.
+  while (value) {
+    unsigned d = (unsigned)(value % (uint64_t)base);
+    value /= (uint64_t)base;
+    if (p != end) {
+      char ch;
+      if (d < 10) {
+        ch = '0' + (char)d;
+      } else {
+        ch = (caps ? 'A' : 'a') + (char)(d - 10);
+      }
+      *p++ = ch;
+    }
+  }
+
+  // Special case for 0.
+  if (p == buf) {
+    if (p != end) {
+      *p++ = '0';
+    }
+  }
+  *p = '\0';
+
+  // Reverse digit string in-place.
+  size_t length = (size_t)(p - buf);
+  for (size_t i = 0, j = length - 1; i < j; ++i, --j) {
+    char ch = buf[i];
+    buf[i] = buf[j];
+    buf[j] = ch;
+  }
+}
+
+static void sh_util_format_integer(char *buf, size_t buf_size, uint64_t value, char conversion) {
+  // Decode the conversion specifier.
+  int is_signed = (conversion == 'd' || conversion == 'i' || conversion == 'o');
+  int base = 10;
+  if (conversion == 'x' || conversion == 'X') {
+    base = 16;
+  } else if (conversion == 'o') {
+    base = 8;
+  }
+  int caps = (conversion == 'X');
+  if (is_signed && (int64_t)(value) < 0) {
+    buf[0] = '-';
+    buf += 1;
+    buf_size -= 1;
+    value = (uint64_t)(-(int64_t)(value));
+  }
+  sh_util_format_unsigned(buf, buf_size, value, base, caps);
+}
+
+// format stream
+typedef struct {
+  size_t total;
+  char *pos;
+  size_t avail;
+} sh_util_stream_t;
+
+static void sh_util_stream_init(sh_util_stream_t *self, char *buffer, size_t buffer_size) {
+  self->total = 0;
+  self->pos = buffer;
+  self->avail = buffer_size;
+
+  if (self->avail > 0) self->pos[0] = '\0';
+}
+
+static size_t sh_util_stream_total(sh_util_stream_t *self) {
+  return self->total;
+}
+
+static void sh_util_stream_send(sh_util_stream_t *self, const char *data, int len) {
+  if (len < 0) {
+    len = (int)strlen(data);
+  }
+  self->total += (size_t)len;
+
+  if (self->avail <= 1) {
+    // no space to put anything else
+    return;
+  }
+
+  if ((size_t)len >= self->avail) {
+    len = (int)(self->avail - 1);
+  }
+
+  memcpy(self->pos, data, (size_t)len);
+  self->pos += len;
+  self->pos[0] = '\0';
+  self->avail -= (size_t)len;
+}
+
+static void sh_util_stream_send_repeat(sh_util_stream_t *self, char ch, int count) {
+  char pad[8];
+  memset(pad, ch, sizeof(pad));
+
+  const int pad_size = (int)(sizeof(pad));
+  while (count > 0) {
+    int avail = count;
+    if (avail > pad_size) {
+      avail = pad_size;
+    }
+    sh_util_stream_send(self, pad, avail);
+    count -= avail;
+  }
+}
+
+static void sh_util_stream_vformat(sh_util_stream_t *self, const char *format, va_list args) {
+  int nn = 0;
+
+  for (;;) {
+    int mm;
+    int padZero = 0;
+    int padLeft = 0;
+    char sign = '\0';
+    int width = -1;
+    int prec = -1;
+    size_t bytelen = sizeof(int);
+    int slen;
+    char buffer[32];  // temporary buffer used to format numbers
+    char c;
+
+    // first, find all characters that are not 0 or '%', then send them to the output directly
+    mm = nn;
+    do {
+      c = format[mm];
+      if (c == '\0' || c == '%') break;
+      mm++;
+    } while (1);
+    if (mm > nn) {
+      sh_util_stream_send(self, format + nn, mm - nn);
+      nn = mm;
+    }
+
+    // is this it ? then exit
+    if (c == '\0') break;
+
+    // nope, we are at a '%' modifier
+    nn++;  // skip it
+
+    // parse flags
+    for (;;) {
+      c = format[nn++];
+      if (c == '\0') {
+        // single trailing '%' ?
+        c = '%';
+        sh_util_stream_send(self, &c, 1);
+        return;
+      } else if (c == '0') {
+        padZero = 1;
+        continue;
+      } else if (c == '-') {
+        padLeft = 1;
+        continue;
+      } else if (c == ' ' || c == '+') {
+        sign = c;
+        continue;
+      }
+      break;
+    }
+
+    // parse field width
+    if ((c >= '0' && c <= '9')) {
+      nn--;
+      width = (int)(sh_util_parse_decimal(format, &nn));
+      c = format[nn++];
+    }
+
+    // parse precision
+    if (c == '.') {
+      prec = (int)(sh_util_parse_decimal(format, &nn));
+      c = format[nn++];
+    }
+
+    // length modifier
+    switch (c) {
+      case 'h':
+        bytelen = sizeof(short);
+        if (format[nn] == 'h') {
+          bytelen = sizeof(char);
+          nn += 1;
+        }
+        c = format[nn++];
+        break;
+      case 'l':
+        bytelen = sizeof(long);
+        if (format[nn] == 'l') {
+          bytelen = sizeof(long long);
+          nn += 1;
+        }
+        c = format[nn++];
+        break;
+      case 'z':
+        bytelen = sizeof(size_t);
+        c = format[nn++];
+        break;
+      case 't':
+        bytelen = sizeof(ptrdiff_t);
+        c = format[nn++];
+        break;
+      default:;
+    }
+
+    // conversion specifier
+    const char *str = buffer;
+    if (c == 's') {
+      // string
+      str = va_arg(args, const char *);
+      if (str == NULL) {
+        str = "(null)";
+      }
+    } else if (c == 'c') {
+      // character
+      // NOTE: char is promoted to int when passed through the stack
+      buffer[0] = (char)(va_arg(args, int));
+      buffer[1] = '\0';
+    } else if (c == 'p') {
+      uint64_t value = (uintptr_t)(va_arg(args, void *));
+      buffer[0] = '0';
+      buffer[1] = 'x';
+      sh_util_format_integer(buffer + 2, sizeof(buffer) - 2, value, 'x');
+    } else if (c == 'd' || c == 'i' || c == 'o' || c == 'u' || c == 'x' || c == 'X') {
+      // integers - first read value from stack
+      uint64_t value;
+      int is_signed = (c == 'd' || c == 'i' || c == 'o');
+      // NOTE: int8_t and int16_t are promoted to int when passed through the stack
+      switch (bytelen) {
+        case 1:
+          value = (uint8_t)(va_arg(args, int));
+          break;
+        case 2:
+          value = (uint16_t)(va_arg(args, int));
+          break;
+        case 4:
+          value = va_arg(args, uint32_t);
+          break;
+        case 8:
+          value = va_arg(args, uint64_t);
+          break;
+        default:
+          return;  // should not happen
+      }
+      // sign extension, if needed
+      if (is_signed) {
+        int shift = (int)(64 - 8 * bytelen);
+        value = (uint64_t)(((int64_t)(value << shift)) >> shift);
+      }
+      // format the number properly into our buffer
+      sh_util_format_integer(buffer, sizeof(buffer), value, c);
+    } else if (c == '%') {
+      buffer[0] = '%';
+      buffer[1] = '\0';
+    } else {
+      //__assert(__FILE__, __LINE__, "conversion specifier unsupported");
+      return;
+    }
+
+    // if we are here, 'str' points to the content that must be outputted.
+    // handle padding and alignment now
+    slen = (int)strlen(str);
+    if (sign != '\0' || prec != -1) {
+      //__assert(__FILE__, __LINE__, "sign/precision unsupported");
+      return;
+    }
+    if (slen < width && !padLeft) {
+      char padChar = padZero ? '0' : ' ';
+      sh_util_stream_send_repeat(self, padChar, width - slen);
+    }
+    sh_util_stream_send(self, str, slen);
+    if (slen < width && padLeft) {
+      char padChar = padZero ? '0' : ' ';
+      sh_util_stream_send_repeat(self, padChar, width - slen);
+    }
+  }
+}
+
+size_t sh_util_vsnprintf(char *buffer, size_t buffer_size, const char *format, va_list args) {
+  sh_util_stream_t stream;
+  sh_util_stream_init(&stream, buffer, buffer_size);
+  sh_util_stream_vformat(&stream, format, args);
+  return sh_util_stream_total(&stream);
+}
+
+size_t sh_util_snprintf(char *buffer, size_t buffer_size, const char *format, ...) {
+  va_list args;
+  va_start(args, format);
+  size_t buffer_len = sh_util_vsnprintf(buffer, buffer_size, format, args);
+  va_end(args);
+  return buffer_len;
+}
diff --git a/app/src/main/cpp/shadowhook/common/sh_util.h b/app/src/main/cpp/shadowhook/common/sh_util.h
new file mode 100644
index 0000000000000000000000000000000000000000..c8d0515cf4ccdb11ed0b57f4759046d1cf6fada2
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/common/sh_util.h
@@ -0,0 +1,96 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#pragma once
+#include <android/api-level.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <time.h>
+
+#define SH_UTIL_ALIGN_START(x, align) ((uintptr_t)(x) & ~((uintptr_t)(align)-1))
+#define SH_UTIL_ALIGN_END(x, align)   (((uintptr_t)(x) + (uintptr_t)(align)-1) & ~((uintptr_t)(align)-1))
+
+#define SH_UTIL_PAGE_START(x) SH_UTIL_ALIGN_START(x, 0x1000)
+#define SH_UTIL_PAGE_END(x)   SH_UTIL_ALIGN_END(x, 0x1000)
+
+#define SH_UTIL_IS_THUMB(addr)   ((addr)&1u)
+#define SH_UTIL_CLEAR_BIT0(addr) ((addr)&0xFFFFFFFE)
+#define SH_UTIL_SET_BIT0(addr)   ((addr) | 1u)
+
+#define SH_UTIL_ALIGN_4(pc) ((pc)&0xFFFFFFFC)
+#define SH_UTIL_SIGN_EXTEND_32(n, len) \
+  ((SH_UTIL_GET_BIT_32(n, len - 1) > 0) ? ((n) | (0xFFFFFFFF << (len))) : n)
+#define SH_UTIL_SIGN_EXTEND_64(n, len) \
+  ((SH_UTIL_GET_BIT_64(n, len - 1) > 0) ? ((n) | (0xFFFFFFFFFFFFFFFF << (len))) : n)
+
+#define SH_UTIL_GET_BIT_16(n, idx)        ((uint16_t)((n) << (15u - (idx))) >> 15u)
+#define SH_UTIL_GET_BITS_16(n, high, low) ((uint16_t)((n) << (15u - (high))) >> (15u - (high) + (low)))
+#define SH_UTIL_GET_BIT_32(n, idx)        ((uint32_t)((n) << (31u - (idx))) >> 31u)
+#define SH_UTIL_GET_BITS_32(n, high, low) ((uint32_t)((n) << (31u - (high))) >> (31u - (high) + (low)))
+#define SH_UTIL_GET_BIT_64(n, idx)        ((uint64_t)((n) << (63u - (idx))) >> 63u)
+#define SH_UTIL_GET_BITS_64(n, high, low) ((uint64_t)((n) << (63u - (high))) >> (63u - (high) + (low)))
+
+#define SH_UTIL_TEMP_FAILURE_RETRY(exp)    \
+  ({                                       \
+    __typeof__(exp) _rc;                   \
+    do {                                   \
+      errno = 0;                           \
+      _rc = (exp);                         \
+    } while (_rc == -1 && errno == EINTR); \
+    _rc;                                   \
+  })
+
+#define SH_UTIL_MAX(a, b)   \
+  ({                        \
+    __typeof__(a) _a = (a); \
+    __typeof__(b) _b = (b); \
+    _a > _b ? _a : _b;      \
+  })
+
+#define SH_UTIL_MIN(a, b)   \
+  ({                        \
+    __typeof__(a) _a = (a); \
+    __typeof__(b) _b = (b); \
+    _a < _b ? _a : _b;      \
+  })
+
+int sh_util_mprotect(uintptr_t addr, size_t len, int prot);
+void sh_util_clear_cache(uintptr_t addr, size_t len);
+
+bool sh_util_is_thumb32(uintptr_t target_addr);
+
+uint32_t sh_util_arm_expand_imm(uint32_t opcode);
+
+int sh_util_write_inst(uintptr_t target_addr, void *inst, size_t inst_len);
+
+int sh_util_get_api_level(void);
+
+int sh_util_write(int fd, const char *buf, size_t buf_len);
+
+struct tm *sh_util_localtime_r(const time_t *timep, long gmtoff, struct tm *result);
+
+size_t sh_util_vsnprintf(char *buffer, size_t buffer_size, const char *format, va_list args);
+size_t sh_util_snprintf(char *buffer, size_t buffer_size, const char *format, ...);
diff --git a/app/src/main/cpp/shadowhook/include/shadowhook.h b/app/src/main/cpp/shadowhook/include/shadowhook.h
new file mode 100644
index 0000000000000000000000000000000000000000..0aa7f1a8b63adf0b71648de849a59437a39dba1d
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/include/shadowhook.h
@@ -0,0 +1,191 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// shadowhook is now credited to all its contributors.
+//
+// Before shadowhook was open sourced in February 2022, it was developed by
+// the following developers from ByteDance's App Health Team:
+//
+// Kelun Cai (caikelun@bytedance.com)
+// Pengying Xu (xupengying@bytedance.com)
+//
+
+#ifndef BYTEDANCE_SHADOWHOOK_H
+#define BYTEDANCE_SHADOWHOOK_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#define SHADOWHOOK_VERSION "1.0.8"
+
+#define SHADOWHOOK_ERRNO_OK                     0
+#define SHADOWHOOK_ERRNO_PENDING                1
+#define SHADOWHOOK_ERRNO_UNINIT                 2
+#define SHADOWHOOK_ERRNO_INVALID_ARG            3
+#define SHADOWHOOK_ERRNO_OOM                    4
+#define SHADOWHOOK_ERRNO_MPROT                  5
+#define SHADOWHOOK_ERRNO_WRITE_CRASH            6
+#define SHADOWHOOK_ERRNO_INIT_ERRNO             7
+#define SHADOWHOOK_ERRNO_INIT_SIGSEGV           8
+#define SHADOWHOOK_ERRNO_INIT_SIGBUS            9
+#define SHADOWHOOK_ERRNO_INIT_ENTER             10
+#define SHADOWHOOK_ERRNO_INIT_SAFE              11
+#define SHADOWHOOK_ERRNO_INIT_LINKER            12
+#define SHADOWHOOK_ERRNO_INIT_HUB               13
+#define SHADOWHOOK_ERRNO_HUB_CREAT              14
+#define SHADOWHOOK_ERRNO_MONITOR_DLOPEN         15
+#define SHADOWHOOK_ERRNO_MONITOR_THREAD         16
+#define SHADOWHOOK_ERRNO_HOOK_DLOPEN_CRASH      17
+#define SHADOWHOOK_ERRNO_HOOK_DLSYM             18
+#define SHADOWHOOK_ERRNO_HOOK_DLSYM_CRASH       19
+#define SHADOWHOOK_ERRNO_HOOK_DUP               20
+#define SHADOWHOOK_ERRNO_HOOK_DLADDR_CRASH      21
+#define SHADOWHOOK_ERRNO_HOOK_DLINFO            22
+#define SHADOWHOOK_ERRNO_HOOK_SYMSZ             23
+#define SHADOWHOOK_ERRNO_HOOK_ENTER             24
+#define SHADOWHOOK_ERRNO_HOOK_REWRITE_CRASH     25
+#define SHADOWHOOK_ERRNO_HOOK_REWRITE_FAILED    26
+#define SHADOWHOOK_ERRNO_UNHOOK_NOTFOUND        27
+#define SHADOWHOOK_ERRNO_UNHOOK_CMP_CRASH       28
+#define SHADOWHOOK_ERRNO_UNHOOK_TRAMPO_MISMATCH 29
+#define SHADOWHOOK_ERRNO_UNHOOK_EXIT_MISMATCH   30
+#define SHADOWHOOK_ERRNO_UNHOOK_EXIT_CRASH      31
+#define SHADOWHOOK_ERRNO_UNHOOK_ON_ERROR        32
+#define SHADOWHOOK_ERRNO_UNHOOK_ON_UNFINISHED   33
+#define SHADOWHOOK_ERRNO_ELF_ARCH_MISMATCH      34
+#define SHADOWHOOK_ERRNO_LINKER_ARCH_MISMATCH   35
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+const char *shadowhook_get_version(void);
+
+// init
+typedef enum {
+  SHADOWHOOK_MODE_SHARED = 0,  // a function can be hooked multiple times
+  SHADOWHOOK_MODE_UNIQUE = 1   // a function can only be hooked once, and hooking again will report an error
+} shadowhook_mode_t;
+int shadowhook_init(shadowhook_mode_t mode, bool debuggable);
+int shadowhook_get_init_errno(void);
+
+// get and set attributes
+#define SHADOWHOOK_IS_SHARED_MODE (SHADOWHOOK_MODE_SHARED == shadowhook_get_mode())
+#define SHADOWHOOK_IS_UNIQUE_MODE (SHADOWHOOK_MODE_UNIQUE == shadowhook_get_mode())
+shadowhook_mode_t shadowhook_get_mode(void);
+bool shadowhook_get_debuggable(void);
+void shadowhook_set_debuggable(bool debuggable);
+bool shadowhook_get_recordable(void);
+void shadowhook_set_recordable(bool recordable);
+
+// get error-number and error message
+int shadowhook_get_errno(void);
+const char *shadowhook_to_errmsg(int error_number);
+
+// hook and unhook
+typedef void (*shadowhook_hooked_t)(int error_number, const char *lib_name, const char *sym_name,
+                                    void *sym_addr, void *new_addr, void *orig_addr, void *arg);
+void *shadowhook_hook_func_addr(void *func_addr, void *new_addr, void **orig_addr);
+void *shadowhook_hook_sym_addr(void *sym_addr, void *new_addr, void **orig_addr);
+void *shadowhook_hook_sym_name(const char *lib_name, const char *sym_name, void *new_addr, void **orig_addr);
+void *shadowhook_hook_sym_name_callback(const char *lib_name, const char *sym_name, void *new_addr,
+                                        void **orig_addr, shadowhook_hooked_t hooked, void *hooked_arg);
+int shadowhook_unhook(void *stub);
+
+// get operation records
+#define SHADOWHOOK_RECORD_ITEM_ALL             0x3FF  // 0b1111111111
+#define SHADOWHOOK_RECORD_ITEM_TIMESTAMP       (1 << 0)
+#define SHADOWHOOK_RECORD_ITEM_CALLER_LIB_NAME (1 << 1)
+#define SHADOWHOOK_RECORD_ITEM_OP              (1 << 2)
+#define SHADOWHOOK_RECORD_ITEM_LIB_NAME        (1 << 3)
+#define SHADOWHOOK_RECORD_ITEM_SYM_NAME        (1 << 4)
+#define SHADOWHOOK_RECORD_ITEM_SYM_ADDR        (1 << 5)
+#define SHADOWHOOK_RECORD_ITEM_NEW_ADDR        (1 << 6)
+#define SHADOWHOOK_RECORD_ITEM_BACKUP_LEN      (1 << 7)
+#define SHADOWHOOK_RECORD_ITEM_ERRNO           (1 << 8)
+#define SHADOWHOOK_RECORD_ITEM_STUB            (1 << 9)
+char *shadowhook_get_records(uint32_t item_flags);
+void shadowhook_dump_records(int fd, uint32_t item_flags);
+
+// helper functions for "get symbol-address from library-name and symbol-name"
+void *shadowhook_dlopen(const char *lib_name);
+void shadowhook_dlclose(void *handle);
+void *shadowhook_dlsym(void *handle, const char *sym_name);
+void *shadowhook_dlsym_dynsym(void *handle, const char *sym_name);
+void *shadowhook_dlsym_symtab(void *handle, const char *sym_name);
+
+// for internal use
+void *shadowhook_get_prev_func(void *func);
+void shadowhook_pop_stack(void *return_address);
+void shadowhook_allow_reentrant(void *return_address);
+void shadowhook_disallow_reentrant(void *return_address);
+void *shadowhook_get_return_address(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+// call previous function in proxy-function
+#ifdef __cplusplus
+#define SHADOWHOOK_CALL_PREV(func, ...) \
+  ((decltype(&(func)))shadowhook_get_prev_func((void *)(func)))(__VA_ARGS__)
+#else
+#define SHADOWHOOK_CALL_PREV(func, func_sig, ...) \
+  ((func_sig)shadowhook_get_prev_func((void *)(func)))(__VA_ARGS__)
+#endif
+// pop stack in proxy-function (for C/C++)
+#define SHADOWHOOK_POP_STACK()                                                        \
+  do {                                                                                \
+    if (SHADOWHOOK_IS_SHARED_MODE) shadowhook_pop_stack(__builtin_return_address(0)); \
+  } while (0)
+
+// pop stack in proxy-function (for C++ only)
+#ifdef __cplusplus
+class ShadowhookStackScope {
+ public:
+  ShadowhookStackScope(void *return_address) : return_address_(return_address) {}
+  ~ShadowhookStackScope() {
+    if (SHADOWHOOK_IS_SHARED_MODE) shadowhook_pop_stack(return_address_);
+  }
+
+ private:
+  void *return_address_;
+};
+#define SHADOWHOOK_STACK_SCOPE() ShadowhookStackScope shadowhook_stack_scope_obj(__builtin_return_address(0))
+#endif
+
+// allow reentrant of the current proxy-function
+#define SHADOWHOOK_ALLOW_REENTRANT()                                                        \
+  do {                                                                                      \
+    if (SHADOWHOOK_IS_SHARED_MODE) shadowhook_allow_reentrant(__builtin_return_address(0)); \
+  } while (0)
+
+// disallow reentrant of the current proxy-function
+#define SHADOWHOOK_DISALLOW_REENTRANT()                                                        \
+  do {                                                                                         \
+    if (SHADOWHOOK_IS_SHARED_MODE) shadowhook_disallow_reentrant(__builtin_return_address(0)); \
+  } while (0)
+
+// get return address in proxy-function
+#define SHADOWHOOK_RETURN_ADDRESS() \
+  ((void *)(SHADOWHOOK_IS_SHARED_MODE ? shadowhook_get_return_address() : __builtin_return_address(0)))
+
+#endif
diff --git a/app/src/main/cpp/shadowhook/sh_enter.c b/app/src/main/cpp/shadowhook/sh_enter.c
new file mode 100644
index 0000000000000000000000000000000000000000..f30b8119861ec5066b34fefa40e3b3dcd0cc69e0
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/sh_enter.c
@@ -0,0 +1,47 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#include "sh_enter.h"
+
+#include <stdint.h>
+
+#include "sh_trampo.h"
+
+#define SH_ENTER_PAGE_NAME "shadowhook-enter"
+#define SH_ENTER_SZ        256
+#define SH_ENTER_DELAY_SEC 10
+
+static sh_trampo_mgr_t sh_enter_trampo_mgr;
+
+int sh_enter_init(void) {
+  sh_trampo_init_mgr(&sh_enter_trampo_mgr, SH_ENTER_PAGE_NAME, SH_ENTER_SZ, SH_ENTER_DELAY_SEC);
+  return 0;
+}
+
+uintptr_t sh_enter_alloc(void) {
+  return sh_trampo_alloc(&sh_enter_trampo_mgr, 0, 0, 0);
+}
+
+void sh_enter_free(uintptr_t enter) {
+  sh_trampo_free(&sh_enter_trampo_mgr, enter);
+}
diff --git a/app/src/main/cpp/shadowhook/sh_enter.h b/app/src/main/cpp/shadowhook/sh_enter.h
new file mode 100644
index 0000000000000000000000000000000000000000..89ba8e59f1afa8e4c5abb03752690c10e6e3b198
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/sh_enter.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#pragma once
+#include <stdint.h>
+
+int sh_enter_init(void);
+
+uintptr_t sh_enter_alloc(void);
+void sh_enter_free(uintptr_t enter);
diff --git a/app/src/main/cpp/shadowhook/sh_exit.c b/app/src/main/cpp/shadowhook/sh_exit.c
new file mode 100644
index 0000000000000000000000000000000000000000..2ae905d9f85fb07bdc7b521ed7c67d3d50fbdf7d
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/sh_exit.c
@@ -0,0 +1,420 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#include "sh_exit.h"
+
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/mman.h>
+
+#include "sh_config.h"
+#include "sh_log.h"
+#include "sh_sig.h"
+#include "sh_trampo.h"
+#include "sh_util.h"
+#include "shadowhook.h"
+#include "xdl.h"
+
+#define SH_EXIT_PAGE_NAME "shadowhook-exit"
+#if defined(__arm__)
+#define SH_EXIT_SZ 8
+#elif defined(__aarch64__)
+#define SH_EXIT_SZ 16
+#endif
+#define SH_EXIT_DELAY_SEC 2
+#define SH_EXIT_GAPS_CAP  16
+
+// Used to identify whether the ELF gap has been zero-filled,
+// and also serves as a guard instruction.
+#if defined(__arm__)
+#define SH_EXIT_CLEAR_FLAG 0xE1200070BE00BE00  // BKPT #0(thumb) + BKPT #0(thumb) + BKPT #0(arm)
+#elif defined(__aarch64__)
+#define SH_EXIT_CLEAR_FLAG 0xD4200000D4200000  // BRK #0 + BRK #0
+#endif
+
+#define SH_EXIT_TYPE_OUT_LIBRARY 0
+#define SH_EXIT_TYPE_IN_LIBRARY  1
+
+extern __attribute((weak)) unsigned long int getauxval(unsigned long int);
+
+static pthread_mutex_t sh_exit_lock = PTHREAD_MUTEX_INITIALIZER;
+static sh_trampo_mgr_t sh_exit_trampo_mgr;
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+typedef struct {
+  uintptr_t load_bias;
+  const ElfW(Phdr) *dlpi_phdr;
+  ElfW(Half) dlpi_phnum;
+} sh_exit_elfinfo_t;
+#pragma clang diagnostic pop
+
+static sh_exit_elfinfo_t sh_exit_app_process_info;
+static sh_exit_elfinfo_t sh_exit_linker_info;
+static sh_exit_elfinfo_t sh_exit_vdso_info;  // vdso may not exist
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wgnu-statement-expression"
+
+static void sh_exit_init_elfinfo(unsigned long type, sh_exit_elfinfo_t *info) {
+  if (__predict_false(NULL == getauxval)) goto err;
+
+  uintptr_t val = (uintptr_t)getauxval(type);
+  if (__predict_false(0 == val)) goto err;
+
+  // get base
+  uintptr_t base = (AT_PHDR == type ? (val & (~0xffful)) : val);
+  if (__predict_false(0 != memcmp((void *)base, ELFMAG, SELFMAG))) goto err;
+
+  // ELF info
+  ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base;
+  const ElfW(Phdr) *dlpi_phdr = (const ElfW(Phdr) *)(base + ehdr->e_phoff);
+  ElfW(Half) dlpi_phnum = ehdr->e_phnum;
+
+  // get load_bias
+  uintptr_t min_vaddr = UINTPTR_MAX;
+  for (size_t i = 0; i < dlpi_phnum; i++) {
+    const ElfW(Phdr) *phdr = &(dlpi_phdr[i]);
+    if (PT_LOAD == phdr->p_type) {
+      if (min_vaddr > phdr->p_vaddr) min_vaddr = phdr->p_vaddr;
+    }
+  }
+  if (__predict_false(UINTPTR_MAX == min_vaddr || base < min_vaddr)) goto err;
+  uintptr_t load_bias = base - min_vaddr;
+
+  info->load_bias = load_bias;
+  info->dlpi_phdr = dlpi_phdr;
+  info->dlpi_phnum = dlpi_phnum;
+  return;
+
+err:
+  info->load_bias = 0;
+  info->dlpi_phdr = NULL;
+  info->dlpi_phnum = 0;
+}
+
+void sh_exit_init(void) {
+  // init for out-library mode
+  sh_trampo_init_mgr(&sh_exit_trampo_mgr, SH_EXIT_PAGE_NAME, SH_EXIT_SZ, SH_EXIT_DELAY_SEC);
+
+  // init for in-library mode
+  sh_exit_init_elfinfo(AT_PHDR, &sh_exit_app_process_info);
+  sh_exit_init_elfinfo(AT_BASE, &sh_exit_linker_info);
+  sh_exit_init_elfinfo(AT_SYSINFO_EHDR, &sh_exit_vdso_info);
+}
+
+// out-library mode:
+//
+// We store the shellcode for exit in mmaped memory near the PC.
+//
+
+static int sh_exit_alloc_out_library(uintptr_t *exit_addr, uintptr_t pc, xdl_info_t *dlinfo, uint8_t *exit,
+                                     size_t exit_len, size_t range_low, size_t range_high) {
+  (void)dlinfo;
+
+  uintptr_t addr = sh_trampo_alloc(&sh_exit_trampo_mgr, pc, range_low, range_high);
+  if (0 == addr) return -1;
+
+  memcpy((void *)addr, exit, exit_len);
+  sh_util_clear_cache(addr, exit_len);
+  *exit_addr = addr;
+  return 0;
+}
+
+static void sh_exit_free_out_library(uintptr_t exit_addr) {
+  sh_trampo_free(&sh_exit_trampo_mgr, exit_addr);
+}
+
+// in-library mode:
+//
+// We store the shellcode for exit in the memory gaps in the ELF.
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+typedef struct {
+  uintptr_t start;
+  uintptr_t end;
+  bool need_fill_zero;
+  bool readable;
+} sh_exit_gap_t;
+#pragma clang diagnostic pop
+
+static size_t sh_exit_get_gaps(xdl_info_t *dlinfo, sh_exit_gap_t *gaps, size_t gaps_cap,
+                               bool elf_loaded_by_kernel) {
+  size_t gaps_used = 0;
+
+  for (size_t i = 0; i < dlinfo->dlpi_phnum; i++) {
+    // current LOAD segment
+    const ElfW(Phdr) *cur_phdr = &(dlinfo->dlpi_phdr[i]);
+    if (PT_LOAD != cur_phdr->p_type) continue;
+
+    // next LOAD segment
+    const ElfW(Phdr) *next_phdr = NULL;
+    if (!elf_loaded_by_kernel) {
+      for (size_t j = i + 1; j < dlinfo->dlpi_phnum; j++) {
+        if (PT_LOAD == dlinfo->dlpi_phdr[j].p_type) {
+          next_phdr = &(dlinfo->dlpi_phdr[j]);
+          break;
+        }
+      }
+    }
+
+    uintptr_t cur_end = (uintptr_t)dlinfo->dli_fbase + cur_phdr->p_vaddr + cur_phdr->p_memsz;
+    uintptr_t cur_page_end = SH_UTIL_PAGE_END(cur_end);
+    uintptr_t cur_file_end = (uintptr_t)dlinfo->dli_fbase + cur_phdr->p_vaddr + cur_phdr->p_filesz;
+    uintptr_t cur_file_page_end = SH_UTIL_PAGE_END(cur_file_end);
+    uintptr_t next_page_start =
+        (NULL == next_phdr ? cur_page_end
+                           : SH_UTIL_PAGE_START((uintptr_t)dlinfo->dli_fbase + next_phdr->p_vaddr));
+
+    sh_exit_gap_t gap = {0, 0, false, false};
+    if (cur_phdr->p_flags & PF_X) {
+      // From: last PF_X page's unused memory tail space.
+      // To: next page start.
+      gap.start = SH_UTIL_ALIGN_END(cur_end, 0x10);
+      gap.end = next_page_start;
+      gap.need_fill_zero = true;
+      gap.readable = (cur_phdr->p_flags & PF_R && cur_page_end == next_page_start);
+    } else if (cur_page_end > cur_file_page_end) {
+      // From: last .bss page(which must NOT be file backend)'s unused memory tail space.
+      // To: next page start.
+      gap.start = SH_UTIL_ALIGN_END(cur_end, 0x10);
+      gap.end = next_page_start;
+      gap.need_fill_zero = false;
+      gap.readable = (cur_phdr->p_flags & PF_R && cur_page_end == next_page_start);
+    } else if (next_page_start > cur_page_end) {
+      // Entire unused memory pages.
+      gap.start = cur_page_end;
+      gap.end = next_page_start;
+      gap.need_fill_zero = true;
+      gap.readable = false;
+    }
+
+    if ((gap.need_fill_zero && gap.end > gap.start + 0x10) || (!gap.need_fill_zero && gap.end > gap.start)) {
+      SH_LOG_INFO("exit: gap, %" PRIxPTR " - %" PRIxPTR " (load_bias %" PRIxPTR ", %" PRIxPTR " - %" PRIxPTR
+                  "), NFZ %d, READABLE %d",
+                  gap.start, gap.end, (uintptr_t)dlinfo->dli_fbase, gap.start - (uintptr_t)dlinfo->dli_fbase,
+                  gap.end - (uintptr_t)dlinfo->dli_fbase, gap.need_fill_zero ? 1 : 0, gap.readable ? 1 : 0);
+      gaps[gaps_used].start = gap.start;
+      gaps[gaps_used].end = gap.end;
+      gaps[gaps_used].need_fill_zero = gap.need_fill_zero;
+      gaps[gaps_used].readable = gap.readable;
+      gaps_used++;
+    }
+
+    if (gaps_used >= gaps_cap) break;
+  }
+
+  return gaps_used;
+}
+
+static bool sh_exit_is_in_elf_range(uintptr_t pc, sh_exit_elfinfo_t *info) {
+  if (pc < info->load_bias) return false;
+
+  uintptr_t vaddr = pc - info->load_bias;
+  for (size_t i = 0; i < info->dlpi_phnum; i++) {
+    const ElfW(Phdr) *phdr = &(info->dlpi_phdr[i]);
+    if (PT_LOAD != phdr->p_type) continue;
+
+    if (phdr->p_vaddr <= vaddr && vaddr < phdr->p_vaddr + phdr->p_memsz) return true;
+  }
+
+  return false;
+}
+
+static bool sh_exit_is_elf_loaded_by_kernel(uintptr_t pc) {
+  if (NULL == sh_exit_app_process_info.dlpi_phdr) return true;
+  if (sh_exit_is_in_elf_range(pc, &sh_exit_app_process_info)) return true;
+
+  if (NULL == sh_exit_linker_info.dlpi_phdr) return true;
+  if (sh_exit_is_in_elf_range(pc, &sh_exit_linker_info)) return true;
+
+  // vdso may not exist
+  if (NULL != sh_exit_vdso_info.dlpi_phdr)
+    if (sh_exit_is_in_elf_range(pc, &sh_exit_vdso_info)) return true;
+
+  return false;
+}
+
+static bool sh_exit_is_zero(uintptr_t buf, size_t buf_len) {
+  for (uintptr_t i = buf; i < buf + buf_len; i += sizeof(uintptr_t))
+    if (*((uintptr_t *)i) != 0) return false;
+
+  return true;
+}
+
+static int sh_exit_fill_zero(uintptr_t start, uintptr_t end, bool readable, xdl_info_t *dlinfo) {
+  size_t size = end - start;
+  bool set_prot_rwx = false;
+
+  if (!readable) {
+    if (0 != sh_util_mprotect(start, size, PROT_READ | PROT_WRITE | PROT_EXEC)) return -1;
+    set_prot_rwx = true;
+  }
+
+  if (*((uint64_t *)start) != SH_EXIT_CLEAR_FLAG) {
+    SH_LOG_INFO("exit: gap fill zero, %" PRIxPTR " - %" PRIxPTR " (load_bias %" PRIxPTR ", %" PRIxPTR
+                " - %" PRIxPTR "), READABLE %d",
+                start, end, (uintptr_t)dlinfo->dli_fbase, start - (uintptr_t)dlinfo->dli_fbase,
+                end - (uintptr_t)dlinfo->dli_fbase, readable ? 1 : 0);
+    if (!set_prot_rwx)
+      if (0 != sh_util_mprotect(start, size, PROT_READ | PROT_WRITE | PROT_EXEC)) return -1;
+    memset((void *)start, 0, size);
+    *((uint64_t *)(start)) = SH_EXIT_CLEAR_FLAG;
+    sh_util_clear_cache(start, size);
+  }
+
+  return 0;
+}
+
+static int sh_exit_try_alloc_in_library(uintptr_t *exit_addr, uintptr_t pc, xdl_info_t *dlinfo, uint8_t *exit,
+                                        size_t exit_len, size_t range_low, size_t range_high, uintptr_t start,
+                                        uintptr_t end) {
+  if (pc >= range_low) start = SH_UTIL_MAX(start, pc - range_low);
+  start = SH_UTIL_ALIGN_END(start, exit_len);
+
+  if (range_high <= UINTPTR_MAX - pc) end = SH_UTIL_MIN(end - exit_len, pc + range_high);
+  end = SH_UTIL_ALIGN_START(end, exit_len);
+
+  if (end < start) return -1;
+  SH_LOG_INFO("exit: gap resize, %" PRIxPTR " - %" PRIxPTR " (load_bias %" PRIxPTR ", %" PRIxPTR
+              " - %" PRIxPTR ")",
+              start, end, (uintptr_t)dlinfo->dli_fbase, start - (uintptr_t)dlinfo->dli_fbase,
+              end - (uintptr_t)dlinfo->dli_fbase);
+
+  for (uintptr_t cur = start; cur <= end; cur += exit_len) {
+    // check if the current space has been used
+    if (!sh_exit_is_zero(cur, exit_len)) continue;
+
+    // write shellcode to the current location
+    if (0 != sh_util_mprotect(cur, exit_len, PROT_READ | PROT_WRITE | PROT_EXEC)) return -1;
+    memcpy((void *)cur, exit, exit_len);
+    sh_util_clear_cache(cur, exit_len);
+    *exit_addr = cur;
+    SH_LOG_INFO("exit: in-library alloc, at %" PRIxPTR " (load_bias %" PRIxPTR ", %" PRIxPTR "), len %zu",
+                cur, (uintptr_t)dlinfo->dli_fbase, cur - (uintptr_t)dlinfo->dli_fbase, exit_len);
+    return 0;
+  }
+  return -1;
+}
+
+static int sh_exit_alloc_in_library(uintptr_t *exit_addr, uintptr_t pc, xdl_info_t *dlinfo, uint8_t *exit,
+                                    size_t exit_len, size_t range_low, size_t range_high) {
+  int r = -1;
+  *exit_addr = 0;
+
+  bool elf_loaded_by_kernel = sh_exit_is_elf_loaded_by_kernel(pc);
+
+  pthread_mutex_lock(&sh_exit_lock);
+
+  SH_SIG_TRY(SIGSEGV, SIGBUS) {
+    sh_exit_gap_t gaps[SH_EXIT_GAPS_CAP];
+    size_t gaps_used = sh_exit_get_gaps(dlinfo, gaps, SH_EXIT_GAPS_CAP, elf_loaded_by_kernel);
+    for (size_t i = 0; i < gaps_used; i++) {
+      // fill zero
+      if (gaps[i].need_fill_zero) {
+        if (0 != sh_exit_fill_zero(gaps[i].start, gaps[i].end, gaps[i].readable, dlinfo)) return -1;
+      }
+
+      if (0 == (r = sh_exit_try_alloc_in_library(exit_addr, pc, dlinfo, exit, exit_len, range_low, range_high,
+                                                 gaps[i].start, gaps[i].end)))
+        break;
+    }
+  }
+  SH_SIG_CATCH() {
+    r = -1;
+    *exit_addr = 0;
+    SH_LOG_WARN("exit: alloc crashed");
+  }
+  SH_SIG_EXIT
+
+  pthread_mutex_unlock(&sh_exit_lock);
+  return r;
+}
+
+static int sh_exit_free_in_library(uintptr_t exit_addr, uint8_t *exit, size_t exit_len) {
+  int r;
+
+  pthread_mutex_lock(&sh_exit_lock);
+
+  SH_SIG_TRY(SIGSEGV, SIGBUS) {
+    if (0 != memcmp((void *)exit_addr, exit, exit_len)) {
+      r = SHADOWHOOK_ERRNO_UNHOOK_EXIT_MISMATCH;
+      goto err;
+    }
+    if (0 != sh_util_mprotect((uintptr_t)exit_addr, exit_len, PROT_READ | PROT_WRITE | PROT_EXEC)) {
+      r = SHADOWHOOK_ERRNO_MPROT;
+      goto err;
+    }
+    memset((void *)exit_addr, 0, exit_len);
+    sh_util_clear_cache((uintptr_t)exit_addr, exit_len);
+    r = 0;
+  err:;
+  }
+  SH_SIG_CATCH() {
+    r = SHADOWHOOK_ERRNO_UNHOOK_EXIT_CRASH;
+    SH_LOG_WARN("exit: free crashed");
+  }
+  SH_SIG_EXIT
+
+  pthread_mutex_unlock(&sh_exit_lock);
+  return r;
+}
+
+int sh_exit_alloc(uintptr_t *exit_addr, uint16_t *exit_type, uintptr_t pc, xdl_info_t *dlinfo, uint8_t *exit,
+                  size_t exit_len, size_t range_low, size_t range_high) {
+  int r;
+
+  // (1) try out-library mode first. Because ELF gaps are a valuable non-renewable resource.
+  *exit_type = SH_EXIT_TYPE_OUT_LIBRARY;
+  r = sh_exit_alloc_out_library(exit_addr, pc, dlinfo, exit, exit_len, range_low, range_high);
+  if (0 == r) goto ok;
+
+  // (2) try in-library mode.
+  *exit_type = SH_EXIT_TYPE_IN_LIBRARY;
+  r = sh_exit_alloc_in_library(exit_addr, pc, dlinfo, exit, exit_len, range_low, range_high);
+  if (0 == r) goto ok;
+
+  return r;
+
+ok:
+  SH_LOG_INFO("exit: alloc %s library, exit %" PRIxPTR ", pc %" PRIxPTR ", distance %" PRIxPTR
+              ", range [-%zx, %zx]",
+              (*exit_type == SH_EXIT_TYPE_OUT_LIBRARY ? "out" : "in"), *exit_addr, pc,
+              (pc > *exit_addr ? pc - *exit_addr : *exit_addr - pc), range_low, range_high);
+  return 0;
+}
+
+int sh_exit_free(uintptr_t exit_addr, uint16_t exit_type, uint8_t *exit, size_t exit_len) {
+  if (SH_EXIT_TYPE_OUT_LIBRARY == exit_type) {
+    (void)exit, (void)exit_len;
+    sh_exit_free_out_library(exit_addr);
+    return 0;
+  } else
+    return sh_exit_free_in_library(exit_addr, exit, exit_len);
+}
+
+#pragma clang diagnostic pop
diff --git a/app/src/main/cpp/shadowhook/sh_exit.h b/app/src/main/cpp/shadowhook/sh_exit.h
new file mode 100644
index 0000000000000000000000000000000000000000..e0f46b60c898972dcf5db9755beac8e42a2acf48
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/sh_exit.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#pragma once
+#include <stddef.h>
+#include <stdint.h>
+
+#include "xdl.h"
+
+void sh_exit_init(void);
+
+int sh_exit_alloc(uintptr_t *exit_addr, uint16_t *exit_type, uintptr_t pc, xdl_info_t *dlinfo, uint8_t *exit,
+                  size_t exit_len, size_t range_low, size_t range_high);
+int sh_exit_free(uintptr_t exit_addr, uint16_t exit_type, uint8_t *exit, size_t exit_len);
diff --git a/app/src/main/cpp/shadowhook/sh_hub.c b/app/src/main/cpp/shadowhook/sh_hub.c
new file mode 100644
index 0000000000000000000000000000000000000000..eef9a3462d9ba38ee645b60698ed8c301239f351
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/sh_hub.c
@@ -0,0 +1,538 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#include "sh_hub.h"
+
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+
+#include "queue.h"
+#include "sh_log.h"
+#include "sh_safe.h"
+#include "sh_sig.h"
+#include "sh_trampo.h"
+#include "sh_util.h"
+#include "shadowhook.h"
+#include "tree.h"
+
+#define SH_HUB_TRAMPO_PAGE_NAME "shadowhook-hub-trampo"
+#define SH_HUB_TRAMPO_DELAY_SEC 5
+#define SH_HUB_STACK_NAME       "shadowhook-hub-stack"
+#define SH_HUB_STACK_SIZE       4096
+#define SH_HUB_STACK_FRAME_MAX  127  // keep sizeof(sh_hub_stack_t) < 4K
+#define SH_HUB_THREAD_MAX       1024
+#define SH_HUB_DELAY_SEC        10
+
+#define SH_HUB_FRAME_FLAG_NONE            ((uintptr_t)0)
+#define SH_HUB_FRAME_FLAG_ALLOW_REENTRANT ((uintptr_t)(1 << 0))
+
+// proxy for each hook-task in the same target-address
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+typedef struct sh_hub_proxy {
+  void *func;
+  bool enabled;
+  SLIST_ENTRY(sh_hub_proxy, ) link;
+} sh_hub_proxy_t;
+#pragma clang diagnostic pop
+
+// proxy list for each hub
+typedef SLIST_HEAD(sh_hub_proxy_list, sh_hub_proxy, ) sh_hub_proxy_list_t;
+
+// frame in the stack
+typedef struct {
+  sh_hub_proxy_list_t proxies;
+  uintptr_t orig_addr;
+  void *return_address;
+  uintptr_t flags;
+} sh_hub_frame_t;
+
+// stack for each thread
+typedef struct {
+  size_t frames_cnt;
+  sh_hub_frame_t frames[SH_HUB_STACK_FRAME_MAX];
+} sh_hub_stack_t;
+
+// hub for each target-address
+struct sh_hub {
+  sh_hub_proxy_list_t proxies;
+  pthread_mutex_t proxies_lock;
+  uintptr_t orig_addr;
+  uintptr_t trampo;
+  time_t destroy_ts;
+  LIST_ENTRY(sh_hub, ) link;
+};
+
+// hub list for delayed-destroy staging
+typedef LIST_HEAD(sh_hub_list, sh_hub, ) sh_hub_list_t;
+
+// global data for hub delayed-destroy staging
+static sh_hub_list_t sh_hub_delayed_destroy;
+static pthread_mutex_t sh_hub_delayed_destroy_lock;
+
+// global data for trampo
+static sh_trampo_mgr_t sh_hub_trampo_mgr;
+
+// global data for stack
+static pthread_key_t sh_hub_stack_tls_key;
+static sh_hub_stack_t *sh_hub_stack_cache;
+static uint8_t *sh_hub_stack_cache_used;
+
+// hub trampoline template
+extern void *sh_hub_trampo_template_data __attribute__((visibility("hidden")));
+__attribute__((naked)) static void sh_hub_trampo_template(void) {
+#if defined(__arm__)
+  __asm__(
+      // Save parameter registers, LR
+      "push  { r0 - r3, lr }     \n"
+
+      // Call sh_hub_push_stack()
+      "ldr   r0, hub_ptr         \n"
+      "mov   r1, lr              \n"
+      "ldr   ip, push_stack      \n"
+      "blx   ip                  \n"
+
+      // Save the hook function's address to IP register
+      "mov   ip, r0              \n"
+
+      // Restore parameter registers, LR
+      "pop   { r0 - r3, lr }     \n"
+
+      // Call hook function
+      "bx    ip                  \n"
+
+      "sh_hub_trampo_template_data:"
+      ".global sh_hub_trampo_template_data;"
+      "push_stack:"
+      ".word 0;"
+      "hub_ptr:"
+      ".word 0;");
+#elif defined(__aarch64__)
+  __asm__(
+      // Save parameter registers, XR(X8), LR
+      "stp   x0, x1, [sp, #-0xd0]!    \n"
+      "stp   x2, x3, [sp, #0x10]      \n"
+      "stp   x4, x5, [sp, #0x20]      \n"
+      "stp   x6, x7, [sp, #0x30]      \n"
+      "stp   x8, lr, [sp, #0x40]      \n"
+      "stp   q0, q1, [sp, #0x50]      \n"
+      "stp   q2, q3, [sp, #0x70]      \n"
+      "stp   q4, q5, [sp, #0x90]      \n"
+      "stp   q6, q7, [sp, #0xb0]      \n"
+
+      // Call sh_hub_push_stack()
+      "ldr   x0, hub_ptr              \n"
+      "mov   x1, lr                   \n"
+      "ldr   x16, push_stack          \n"
+      "blr   x16                      \n"
+
+      // Save the hook function's address to IP register
+      "mov   x16, x0                  \n"
+
+      // Restore parameter registers, XR(X8), LR
+      "ldp   q6, q7, [sp, #0xb0]      \n"
+      "ldp   q4, q5, [sp, #0x90]      \n"
+      "ldp   q2, q3, [sp, #0x70]      \n"
+      "ldp   q0, q1, [sp, #0x50]      \n"
+      "ldp   x8, lr, [sp, #0x40]      \n"
+      "ldp   x6, x7, [sp, #0x30]      \n"
+      "ldp   x4, x5, [sp, #0x20]      \n"
+      "ldp   x2, x3, [sp, #0x10]      \n"
+      "ldp   x0, x1, [sp], #0xd0      \n"
+
+      // Call hook function
+      "br    x16                      \n"
+
+      "sh_hub_trampo_template_data:"
+      ".global sh_hub_trampo_template_data;"
+      "push_stack:"
+      ".quad 0;"
+      "hub_ptr:"
+      ".quad 0;");
+#endif
+}
+
+static void *sh_hub_trampo_template_start(void) {
+#if defined(__arm__) && defined(__thumb__)
+  return (void *)((uintptr_t)&sh_hub_trampo_template - 1);
+#else
+  return (void *)&sh_hub_trampo_template;
+#endif
+}
+
+static sh_hub_stack_t *sh_hub_stack_create(void) {
+  // get stack from global cache
+  for (size_t i = 0; i < SH_HUB_THREAD_MAX; i++) {
+    uint8_t *used = &(sh_hub_stack_cache_used[i]);
+    if (0 == *used) {
+      uint8_t expected = 0;
+      if (__atomic_compare_exchange_n(used, &expected, 1, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) {
+        sh_hub_stack_t *stack = &(sh_hub_stack_cache[i]);
+        stack->frames_cnt = 0;
+        SH_LOG_DEBUG("hub: get stack from global cache[%zu] %p", i, (void *)stack);
+        return stack;  // OK
+      }
+    }
+  }
+
+  // create new stack by mmap
+  int prot = PROT_READ | PROT_WRITE;
+  int flags = MAP_PRIVATE | MAP_ANONYMOUS;
+  void *buf = sh_safe_mmap(NULL, SH_HUB_STACK_SIZE, prot, flags, -1, 0);
+  if (MAP_FAILED == buf) return NULL;  // failed
+  sh_safe_prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, (unsigned long)buf, SH_HUB_STACK_SIZE,
+                (unsigned long)SH_HUB_STACK_NAME);
+  sh_hub_stack_t *stack = (sh_hub_stack_t *)buf;
+  stack->frames_cnt = 0;
+  return stack;  // OK
+}
+
+static void sh_hub_stack_destroy(void *buf) {
+  if (NULL == buf) return;
+
+  if ((uintptr_t)sh_hub_stack_cache <= (uintptr_t)buf &&
+      (uintptr_t)buf < ((uintptr_t)sh_hub_stack_cache + SH_HUB_THREAD_MAX * sizeof(sh_hub_stack_t))) {
+    // return stack to global cache
+    size_t i = ((uintptr_t)buf - (uintptr_t)sh_hub_stack_cache) / sizeof(sh_hub_stack_t);
+    uint8_t *used = &(sh_hub_stack_cache_used[i]);
+    if (1 != *used) abort();
+    __atomic_store_n(used, 0, __ATOMIC_RELEASE);
+    SH_LOG_DEBUG("hub: return stack to global cache[%zu] %p", i, buf);
+  } else {
+    // munmap stack
+    munmap(buf, SH_HUB_STACK_SIZE);
+  }
+}
+
+int sh_hub_init(void) {
+  LIST_INIT(&sh_hub_delayed_destroy);
+  pthread_mutex_init(&sh_hub_delayed_destroy_lock, NULL);
+
+  // init TLS key
+  if (__predict_false(0 != pthread_key_create(&sh_hub_stack_tls_key, sh_hub_stack_destroy))) return -1;
+
+  // init hub's stack cache
+  if (__predict_false(NULL == (sh_hub_stack_cache = malloc(SH_HUB_THREAD_MAX * sizeof(sh_hub_stack_t)))))
+    return -1;
+  if (__predict_false(NULL == (sh_hub_stack_cache_used = calloc(SH_HUB_THREAD_MAX, sizeof(uint8_t)))))
+    return -1;
+
+  // init hub's trampoline manager
+  size_t code_size = (uintptr_t)(&sh_hub_trampo_template_data) - (uintptr_t)(sh_hub_trampo_template_start());
+  size_t data_size = sizeof(void *) + sizeof(void *);
+  sh_trampo_init_mgr(&sh_hub_trampo_mgr, SH_HUB_TRAMPO_PAGE_NAME, code_size + data_size,
+                     SH_HUB_TRAMPO_DELAY_SEC);
+
+  return 0;
+}
+
+static void *sh_hub_push_stack(sh_hub_t *self, void *return_address) {
+  sh_hub_stack_t *stack = (sh_hub_stack_t *)sh_safe_pthread_getspecific(sh_hub_stack_tls_key);
+
+  // create stack, only once
+  if (__predict_false(NULL == stack)) {
+    if (__predict_false(NULL == (stack = sh_hub_stack_create()))) goto end;
+    sh_safe_pthread_setspecific(sh_hub_stack_tls_key, (void *)stack);
+  }
+
+  // check whether a recursive call occurred
+  bool recursive = false;
+  for (size_t i = stack->frames_cnt; i > 0; i--) {
+    sh_hub_frame_t *frame = &stack->frames[i - 1];
+    if (0 == (frame->flags & SH_HUB_FRAME_FLAG_ALLOW_REENTRANT) && (frame->orig_addr == self->orig_addr)) {
+      // recursive call found
+      recursive = true;
+      break;
+    }
+  }
+
+  // find and return the first enabled proxy's function in the proxy-list
+  // (does not include the original function)
+  if (!recursive) {
+    sh_hub_proxy_t *proxy;
+    SLIST_FOREACH(proxy, &self->proxies, link) {
+      if (proxy->enabled) {
+        // push a new frame for the current proxy
+        if (stack->frames_cnt >= SH_HUB_STACK_FRAME_MAX) goto end;
+        stack->frames_cnt++;
+        SH_LOG_DEBUG("hub: frames_cnt++ = %zu", stack->frames_cnt);
+        sh_hub_frame_t *frame = &stack->frames[stack->frames_cnt - 1];
+        frame->proxies = self->proxies;
+        frame->orig_addr = self->orig_addr;
+        frame->return_address = return_address;
+        frame->flags = SH_HUB_FRAME_FLAG_NONE;
+
+        // return the first enabled proxy's function
+        SH_LOG_DEBUG("hub: push_stack() return first enabled proxy %p", proxy->func);
+        return proxy->func;
+      }
+    }
+  }
+
+  // if not found enabled proxy in the proxy-list, or recursive call found,
+  // just return the original-function
+end:
+  SH_LOG_DEBUG("hub: push_stack() return orig_addr %p", (void *)self->orig_addr);
+  return (void *)self->orig_addr;
+}
+
+void sh_hub_pop_stack(void *return_address) {
+  sh_hub_stack_t *stack = (sh_hub_stack_t *)sh_safe_pthread_getspecific(sh_hub_stack_tls_key);
+  if (0 == stack->frames_cnt) return;
+  sh_hub_frame_t *frame = &stack->frames[stack->frames_cnt - 1];
+
+  // only the first proxy will actually execute pop-stack()
+  if (frame->return_address == return_address) {
+    stack->frames_cnt--;
+    SH_LOG_DEBUG("hub: frames_cnt-- = %zu", stack->frames_cnt);
+  }
+}
+
+sh_hub_t *sh_hub_create(uintptr_t target_addr, uintptr_t *trampo) {
+  size_t code_size = (uintptr_t)(&sh_hub_trampo_template_data) - (uintptr_t)(sh_hub_trampo_template_start());
+  size_t data_size = sizeof(void *) + sizeof(void *);
+
+  sh_hub_t *self = malloc(sizeof(sh_hub_t));
+  if (NULL == self) return NULL;
+  SLIST_INIT(&self->proxies);
+  pthread_mutex_init(&self->proxies_lock, NULL);
+  self->orig_addr = 0;
+
+  // alloc memory for trampoline
+  if (0 == (self->trampo = sh_trampo_alloc(&sh_hub_trampo_mgr, 0, 0, 0))) {
+    free(self);
+    return NULL;
+  }
+
+  // fill in code
+  SH_SIG_TRY(SIGSEGV, SIGBUS) {
+    memcpy((void *)self->trampo, sh_hub_trampo_template_start(), code_size);
+  }
+  SH_SIG_CATCH() {
+    sh_trampo_free(&sh_hub_trampo_mgr, self->trampo);
+    free(self);
+    SH_LOG_WARN("hub: fill in code crashed");
+    return NULL;
+  }
+  SH_SIG_EXIT
+
+  // file in data
+  void **data = (void **)(self->trampo + code_size);
+  *data++ = (void *)sh_hub_push_stack;
+  *data = (void *)self;
+
+  // clear CPU cache
+  sh_util_clear_cache(self->trampo, code_size + data_size);
+
+#if defined(__arm__) && defined(__thumb__)
+  *trampo = self->trampo + 1;
+#else
+  *trampo = self->trampo;
+#endif
+
+  SH_LOG_INFO("hub: create trampo for target_addr %" PRIxPTR " at %" PRIxPTR ", size %zu + %zu = %zu",
+              target_addr, *trampo, code_size, data_size, code_size + data_size);
+  return self;
+}
+
+static void sh_hub_destroy_inner(sh_hub_t *self) {
+  pthread_mutex_destroy(&self->proxies_lock);
+
+  if (0 != self->trampo) sh_trampo_free(&sh_hub_trampo_mgr, self->trampo);
+
+  while (!SLIST_EMPTY(&self->proxies)) {
+    sh_hub_proxy_t *proxy = SLIST_FIRST(&self->proxies);
+    SLIST_REMOVE_HEAD(&self->proxies, link);
+    free(proxy);
+  }
+
+  free(self);
+}
+
+void sh_hub_destroy(sh_hub_t *self, bool with_delay) {
+  if (SHADOWHOOK_IS_SHARED_MODE) {
+    struct timeval now;
+    gettimeofday(&now, NULL);
+
+    if (!LIST_EMPTY(&sh_hub_delayed_destroy)) {
+      pthread_mutex_lock(&sh_hub_delayed_destroy_lock);
+      sh_hub_t *hub, *hub_tmp;
+      LIST_FOREACH_SAFE(hub, &sh_hub_delayed_destroy, link, hub_tmp)
+      if (now.tv_sec - hub->destroy_ts > SH_HUB_DELAY_SEC) {
+        LIST_REMOVE(hub, link);
+        sh_hub_destroy_inner(hub);
+      }
+      pthread_mutex_unlock(&sh_hub_delayed_destroy_lock);
+    }
+
+    if (with_delay) {
+      self->destroy_ts = now.tv_sec;
+      sh_trampo_free(&sh_hub_trampo_mgr, self->trampo);
+      self->trampo = 0;
+
+      pthread_mutex_lock(&sh_hub_delayed_destroy_lock);
+      LIST_INSERT_HEAD(&sh_hub_delayed_destroy, self, link);
+      pthread_mutex_unlock(&sh_hub_delayed_destroy_lock);
+    } else
+      sh_hub_destroy_inner(self);
+  } else
+    sh_hub_destroy_inner(self);
+}
+
+uintptr_t sh_hub_get_orig_addr(sh_hub_t *self) {
+  return self->orig_addr;
+}
+
+uintptr_t *sh_hub_get_orig_addr_addr(sh_hub_t *self) {
+  return &self->orig_addr;
+}
+
+int sh_hub_add_proxy(sh_hub_t *self, uintptr_t func) {
+  int r = SHADOWHOOK_ERRNO_OK;
+
+  pthread_mutex_lock(&self->proxies_lock);
+
+  // check repeated funcion
+  sh_hub_proxy_t *proxy;
+  SLIST_FOREACH(proxy, &self->proxies, link) {
+    if (proxy->enabled && proxy->func == (void *)func) {
+      r = SHADOWHOOK_ERRNO_HOOK_DUP;
+      goto end;
+    }
+  }
+
+  // try to re-enable an exists item
+  SLIST_FOREACH(proxy, &self->proxies, link) {
+    if (proxy->func == (void *)func) {
+      if (!proxy->enabled) __atomic_store_n((bool *)&proxy->enabled, true, __ATOMIC_SEQ_CST);
+
+      SH_LOG_INFO("hub: add(re-enable) func %" PRIxPTR, func);
+      goto end;
+    }
+  }
+
+  // create new item
+  if (NULL == (proxy = malloc(sizeof(sh_hub_proxy_t)))) {
+    r = SHADOWHOOK_ERRNO_OOM;
+    goto end;
+  }
+  proxy->func = (void *)func;
+  proxy->enabled = true;
+
+  // insert to the head of the proxy-list
+  // equivalent to: SLIST_INSERT_HEAD(&self->proxies, proxy, link);
+  // but: __ATOMIC_RELEASE ensures readers see only fully-constructed item
+  SLIST_NEXT(proxy, link) = SLIST_FIRST(&self->proxies);
+  __atomic_store_n((uintptr_t *)(&SLIST_FIRST(&self->proxies)), (uintptr_t)proxy, __ATOMIC_RELEASE);
+  SH_LOG_INFO("hub: add(new) func %" PRIxPTR, func);
+
+end:
+  pthread_mutex_unlock(&self->proxies_lock);
+  return r;
+}
+
+int sh_hub_del_proxy(sh_hub_t *self, uintptr_t func, bool *have_enabled_proxy) {
+  *have_enabled_proxy = false;
+
+  pthread_mutex_lock(&self->proxies_lock);
+
+  sh_hub_proxy_t *proxy;
+  bool deleted = false;
+  SLIST_FOREACH(proxy, &self->proxies, link) {
+    if (proxy->func == (void *)func) {
+      if (proxy->enabled) __atomic_store_n((bool *)&proxy->enabled, false, __ATOMIC_SEQ_CST);
+
+      deleted = true;
+      SH_LOG_INFO("hub: del func %" PRIxPTR, func);
+    }
+
+    if (proxy->enabled && !*have_enabled_proxy) *have_enabled_proxy = true;
+
+    if (deleted && *have_enabled_proxy) break;
+  }
+
+  pthread_mutex_unlock(&self->proxies_lock);
+
+  return deleted ? 0 : -1;
+}
+
+static sh_hub_frame_t *sh_hub_get_current_frame(void *return_address) {
+  sh_hub_stack_t *stack = (sh_hub_stack_t *)sh_safe_pthread_getspecific(sh_hub_stack_tls_key);
+  if (0 == stack->frames_cnt) return NULL;
+  sh_hub_frame_t *frame = &stack->frames[stack->frames_cnt - 1];
+  return frame->return_address == return_address ? frame : NULL;
+}
+
+void sh_hub_allow_reentrant(void *return_address) {
+  sh_hub_frame_t *frame = sh_hub_get_current_frame(return_address);
+  if (NULL != frame) {
+    frame->flags |= SH_HUB_FRAME_FLAG_ALLOW_REENTRANT;
+    SH_LOG_DEBUG("hub: allow reentrant frame %p", return_address);
+  }
+}
+
+void sh_hub_disallow_reentrant(void *return_address) {
+  sh_hub_frame_t *frame = sh_hub_get_current_frame(return_address);
+  if (NULL != frame) {
+    frame->flags &= ~SH_HUB_FRAME_FLAG_ALLOW_REENTRANT;
+    SH_LOG_DEBUG("hub: disallow reentrant frame %p", return_address);
+  }
+}
+
+void *sh_hub_get_prev_func(void *func) {
+  sh_hub_stack_t *stack = (sh_hub_stack_t *)sh_safe_pthread_getspecific(sh_hub_stack_tls_key);
+  if (0 == stack->frames_cnt) sh_safe_abort();  // called in a non-hook status?
+  sh_hub_frame_t *frame = &stack->frames[stack->frames_cnt - 1];
+
+  // find and return the next enabled hook-function in the hook-chain
+  bool found = false;
+  sh_hub_proxy_t *proxy;
+  SLIST_FOREACH(proxy, &(frame->proxies), link) {
+    if (!found) {
+      if (proxy->func == func) found = true;
+    } else {
+      if (proxy->enabled) break;
+    }
+  }
+  if (NULL != proxy) {
+    SH_LOG_DEBUG("hub: get_prev_func() return next enabled proxy %p", proxy->func);
+    return proxy->func;
+  }
+
+  SH_LOG_DEBUG("hub: get_prev_func() return orig_addr %p", (void *)frame->orig_addr);
+  // did not find, return the original-function
+  return (void *)frame->orig_addr;
+}
+
+void *sh_hub_get_return_address(void) {
+  sh_hub_stack_t *stack = (sh_hub_stack_t *)sh_safe_pthread_getspecific(sh_hub_stack_tls_key);
+  if (0 == stack->frames_cnt) sh_safe_abort();  // called in a non-hook status?
+  sh_hub_frame_t *frame = &stack->frames[stack->frames_cnt - 1];
+
+  return frame->return_address;
+}
diff --git a/app/src/main/cpp/shadowhook/sh_hub.h b/app/src/main/cpp/shadowhook/sh_hub.h
new file mode 100644
index 0000000000000000000000000000000000000000..accbfb5f2c2c2dc588e81d9c530c296636704012
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/sh_hub.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#pragma once
+#include <stdbool.h>
+#include <stdint.h>
+
+int sh_hub_init(void);
+
+typedef struct sh_hub sh_hub_t;
+
+sh_hub_t *sh_hub_create(uintptr_t target_addr, uintptr_t *trampo);
+void sh_hub_destroy(sh_hub_t *self, bool with_delay);
+
+uintptr_t sh_hub_get_orig_addr(sh_hub_t *self);
+uintptr_t *sh_hub_get_orig_addr_addr(sh_hub_t *self);
+
+int sh_hub_add_proxy(sh_hub_t *self, uintptr_t func);
+int sh_hub_del_proxy(sh_hub_t *self, uintptr_t func, bool *have_enabled_proxy);
+
+void *sh_hub_get_prev_func(void *func);
+void sh_hub_pop_stack(void *return_address);
+void sh_hub_allow_reentrant(void *return_address);
+void sh_hub_disallow_reentrant(void *return_address);
+void *sh_hub_get_return_address(void);
diff --git a/app/src/main/cpp/shadowhook/sh_jni.c b/app/src/main/cpp/shadowhook/sh_jni.c
new file mode 100644
index 0000000000000000000000000000000000000000..051f6d992ac692eb05fd8e7c262e8085773e10f8
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/sh_jni.c
@@ -0,0 +1,139 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#include <errno.h>
+#include <jni.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "sh_log.h"
+#include "shadowhook.h"
+
+#define SH_JNI_VERSION    JNI_VERSION_1_6
+#define SH_JNI_CLASS_NAME "com/bytedance/shadowhook/ShadowHook"
+
+static jstring sh_jni_get_version(JNIEnv *env, jobject thiz) {
+  (void)thiz;
+  return (*env)->NewStringUTF(env, shadowhook_get_version());
+}
+
+static jint sh_jni_init(JNIEnv *env, jobject thiz, jint mode, jboolean debuggable) {
+  (void)env, (void)thiz;
+
+  return shadowhook_init(0 == mode ? SHADOWHOOK_MODE_SHARED : SHADOWHOOK_MODE_UNIQUE, (bool)debuggable);
+}
+
+static jint sh_jni_get_init_errno(JNIEnv *env, jobject thiz) {
+  (void)env, (void)thiz;
+
+  return shadowhook_get_init_errno();
+}
+
+static jint sh_jni_get_mode(JNIEnv *env, jobject thiz) {
+  (void)env, (void)thiz;
+
+  return SHADOWHOOK_MODE_SHARED == shadowhook_get_mode() ? 0 : 1;
+}
+
+static jboolean sh_jni_get_debuggable(JNIEnv *env, jobject thiz) {
+  (void)env, (void)thiz;
+
+  return shadowhook_get_debuggable();
+}
+
+static void sh_jni_set_debuggable(JNIEnv *env, jobject thiz, jboolean debuggable) {
+  (void)env, (void)thiz;
+
+  shadowhook_set_debuggable((bool)debuggable);
+}
+
+static jboolean sh_jni_get_recordable(JNIEnv *env, jobject thiz) {
+  (void)env, (void)thiz;
+
+  return shadowhook_get_recordable();
+}
+
+static void sh_jni_set_recordable(JNIEnv *env, jobject thiz, jboolean recordable) {
+  (void)env, (void)thiz;
+
+  shadowhook_set_recordable((bool)recordable);
+}
+
+static jstring sh_jni_to_errmsg(JNIEnv *env, jobject thiz, jint error_number) {
+  (void)thiz;
+
+  return (*env)->NewStringUTF(env, shadowhook_to_errmsg(error_number));
+}
+
+static jstring sh_jni_get_records(JNIEnv *env, jobject thiz, jint item_flags) {
+  (void)thiz;
+
+  char *str = shadowhook_get_records((uint32_t)item_flags);
+  if (NULL == str) return NULL;
+
+  jstring jstr = (*env)->NewStringUTF(env, str);
+  free(str);
+  return jstr;
+}
+
+static jstring sh_jni_get_arch(JNIEnv *env, jobject thiz) {
+  (void)thiz;
+
+#if defined(__arm__)
+  char *arch = "arm";
+#elif defined(__aarch64__)
+  char *arch = "arm64";
+#else
+  char *arch = "unsupported";
+#endif
+
+  return (*env)->NewStringUTF(env, arch);
+}
+
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
+  (void)reserved;
+
+  if (__predict_false(NULL == vm)) return JNI_ERR;
+
+  JNIEnv *env;
+  if (__predict_false(JNI_OK != (*vm)->GetEnv(vm, (void **)&env, SH_JNI_VERSION))) return JNI_ERR;
+  if (__predict_false(NULL == env || NULL == *env)) return JNI_ERR;
+
+  jclass cls;
+  if (__predict_false(NULL == (cls = (*env)->FindClass(env, SH_JNI_CLASS_NAME)))) return JNI_ERR;
+
+  JNINativeMethod m[] = {{"nativeGetVersion", "()Ljava/lang/String;", (void *)sh_jni_get_version},
+                         {"nativeInit", "(IZ)I", (void *)sh_jni_init},
+                         {"nativeGetInitErrno", "()I", (void *)sh_jni_get_init_errno},
+                         {"nativeGetMode", "()I", (void *)sh_jni_get_mode},
+                         {"nativeGetDebuggable", "()Z", (void *)sh_jni_get_debuggable},
+                         {"nativeSetDebuggable", "(Z)V", (void *)sh_jni_set_debuggable},
+                         {"nativeGetRecordable", "()Z", (void *)sh_jni_get_recordable},
+                         {"nativeSetRecordable", "(Z)V", (void *)sh_jni_set_recordable},
+                         {"nativeToErrmsg", "(I)Ljava/lang/String;", (void *)sh_jni_to_errmsg},
+                         {"nativeGetRecords", "(I)Ljava/lang/String;", (void *)sh_jni_get_records},
+                         {"nativeGetArch", "()Ljava/lang/String;", (void *)sh_jni_get_arch}};
+  if (__predict_false(0 != (*env)->RegisterNatives(env, cls, m, sizeof(m) / sizeof(m[0])))) return JNI_ERR;
+
+  return SH_JNI_VERSION;
+}
diff --git a/app/src/main/cpp/shadowhook/sh_linker.c b/app/src/main/cpp/shadowhook/sh_linker.c
new file mode 100644
index 0000000000000000000000000000000000000000..2cf3a2ebd5328ed51e645a3a099103e56769d82f
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/sh_linker.c
@@ -0,0 +1,409 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#include "sh_linker.h"
+
+#include <dlfcn.h>
+#include <pthread.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "sh_log.h"
+#include "sh_recorder.h"
+#include "sh_sig.h"
+#include "sh_switch.h"
+#include "sh_util.h"
+#include "shadowhook.h"
+#include "xdl.h"
+
+#ifndef __LP64__
+#define SH_LINKER_BASENAME "linker"
+#else
+#define SH_LINKER_BASENAME "linker64"
+#endif
+
+#define SH_LINKER_SYM_G_DL_MUTEX  "__dl__ZL10g_dl_mutex"
+#define SH_LINKER_SYM_DO_DLOPEN_L "__dl__Z9do_dlopenPKciPK17android_dlextinfo"
+#define SH_LINKER_SYM_DO_DLOPEN_N "__dl__Z9do_dlopenPKciPK17android_dlextinfoPv"
+#define SH_LINKER_SYM_DO_DLOPEN_O "__dl__Z9do_dlopenPKciPK17android_dlextinfoPKv"
+
+static bool sh_linker_dlopen_hooked = false;
+
+static sh_linker_post_dlopen_t sh_linker_post_dlopen;
+static void *sh_linker_post_dlopen_arg;
+
+static pthread_mutex_t *sh_linker_g_dl_mutex;
+static uintptr_t sh_linker_dlopen_addr;  // save address of dlopen(==4.x) or do_dlopen(>=5.0)
+static xdl_info_t sh_linker_dlopen_dlinfo;
+
+#if defined(__arm__) && __ANDROID_API__ < __ANDROID_API_L__
+static uintptr_t sh_linker_dlfcn[6];
+static const char *sh_linker_dlfcn_name[6] = {"dlopen", "dlerror", "dlsym",
+                                              "dladdr", "dlclose", "dl_unwind_find_exidx"};
+#endif
+
+__attribute__((constructor)) static void sh_linker_ctor(void) {
+  sh_linker_dlopen_addr = (uintptr_t)dlopen;
+#if defined(__arm__) && __ANDROID_API__ < __ANDROID_API_L__
+  sh_linker_dlfcn[0] = (uintptr_t)dlopen;
+  sh_linker_dlfcn[1] = (uintptr_t)dlerror;
+  sh_linker_dlfcn[2] = (uintptr_t)dlsym;
+  sh_linker_dlfcn[3] = (uintptr_t)dladdr;
+  sh_linker_dlfcn[4] = (uintptr_t)dlclose;
+  sh_linker_dlfcn[5] = (uintptr_t)dl_unwind_find_exidx;
+#endif
+}
+
+static void *sh_linker_get_base_addr(xdl_info_t *dlinfo) {
+  uintptr_t vaddr_min = UINTPTR_MAX;
+  for (size_t i = 0; i < dlinfo->dlpi_phnum; i++) {
+    const ElfW(Phdr) *phdr = &(dlinfo->dlpi_phdr[i]);
+    if (PT_LOAD == phdr->p_type && vaddr_min > phdr->p_vaddr) vaddr_min = phdr->p_vaddr;
+  }
+
+  if (UINTPTR_MAX == vaddr_min)
+    return dlinfo->dli_fbase;  // should not happen
+  else
+    return (void *)((uintptr_t)dlinfo->dli_fbase + SH_UTIL_PAGE_START(vaddr_min));
+}
+
+static bool sh_linker_check_arch(xdl_info_t *dlinfo) {
+  ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)sh_linker_get_base_addr(dlinfo);
+
+#if defined(__LP64__)
+#define SH_LINKER_ELF_CLASS   ELFCLASS64
+#define SH_LINKER_ELF_MACHINE EM_AARCH64
+#else
+#define SH_LINKER_ELF_CLASS   ELFCLASS32
+#define SH_LINKER_ELF_MACHINE EM_ARM
+#endif
+
+  if (0 != memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) return false;
+  if (SH_LINKER_ELF_CLASS != ehdr->e_ident[EI_CLASS]) return false;
+  if (SH_LINKER_ELF_MACHINE != ehdr->e_machine) return false;
+
+  return true;
+}
+
+int sh_linker_init(void) {
+  memset(&sh_linker_dlopen_dlinfo, 0, sizeof(sh_linker_dlopen_dlinfo));
+
+  int api_level = sh_util_get_api_level();
+  if (__predict_true(api_level >= __ANDROID_API_L__)) {
+    sh_linker_dlopen_addr = 0;
+
+    void *handle = xdl_open(SH_LINKER_BASENAME, XDL_DEFAULT);
+    if (__predict_false(NULL == handle)) return -1;
+    xdl_info(handle, XDL_DI_DLINFO, (void *)&sh_linker_dlopen_dlinfo);
+    sh_linker_dlopen_dlinfo.dli_fname = SH_LINKER_BASENAME;
+
+    // get g_dl_mutex
+    sh_linker_g_dl_mutex = (pthread_mutex_t *)(xdl_dsym(handle, SH_LINKER_SYM_G_DL_MUTEX, NULL));
+
+    // get do_dlopen
+    if (api_level >= __ANDROID_API_O__)
+      sh_linker_dlopen_dlinfo.dli_sname = SH_LINKER_SYM_DO_DLOPEN_O;
+    else if (api_level >= __ANDROID_API_N__)
+      sh_linker_dlopen_dlinfo.dli_sname = SH_LINKER_SYM_DO_DLOPEN_N;
+    else
+      sh_linker_dlopen_dlinfo.dli_sname = SH_LINKER_SYM_DO_DLOPEN_L;
+    sh_linker_dlopen_dlinfo.dli_saddr =
+        xdl_dsym(handle, sh_linker_dlopen_dlinfo.dli_sname, &(sh_linker_dlopen_dlinfo.dli_ssize));
+    sh_linker_dlopen_addr = (uintptr_t)sh_linker_dlopen_dlinfo.dli_saddr;
+
+    xdl_close(handle);
+  }
+
+  return (0 != sh_linker_dlopen_addr && (0 != sh_linker_g_dl_mutex || api_level < __ANDROID_API_L__)) ? 0
+                                                                                                      : -1;
+}
+
+const char *sh_linker_match_dlfcn(uintptr_t target_addr) {
+#if defined(__arm__) && __ANDROID_API__ < __ANDROID_API_L__
+  if (sh_util_get_api_level() < __ANDROID_API_L__)
+    for (size_t i = 0; i < sizeof(sh_linker_dlfcn) / sizeof(sh_linker_dlfcn[0]); i++)
+      if (sh_linker_dlfcn[i] == target_addr) return sh_linker_dlfcn_name[i];
+#else
+  (void)target_addr;
+#endif
+
+  return NULL;
+}
+
+bool sh_linker_need_to_hook_dlopen(uintptr_t target_addr) {
+  return SHADOWHOOK_IS_UNIQUE_MODE && !sh_linker_dlopen_hooked && target_addr == sh_linker_dlopen_addr;
+}
+
+typedef void *(*sh_linker_proxy_dlopen_t)(const char *, int);
+static sh_linker_proxy_dlopen_t sh_linker_orig_dlopen;
+static void *sh_linker_proxy_dlopen(const char *filename, int flag) {
+  void *handle;
+  if (SHADOWHOOK_IS_SHARED_MODE)
+    handle = SHADOWHOOK_CALL_PREV(sh_linker_proxy_dlopen, sh_linker_proxy_dlopen_t, filename, flag);
+  else
+    handle = sh_linker_orig_dlopen(filename, flag);
+
+  if (NULL != handle) sh_linker_post_dlopen(sh_linker_post_dlopen_arg);
+
+  if (SHADOWHOOK_IS_SHARED_MODE) SHADOWHOOK_POP_STACK();
+  return handle;
+}
+
+typedef void *(*sh_linker_proxy_do_dlopen_l_t)(const char *, int, const void *);
+static sh_linker_proxy_do_dlopen_l_t sh_linker_orig_do_dlopen_l;
+static void *sh_linker_proxy_do_dlopen_l(const char *name, int flags, const void *extinfo) {
+  void *handle;
+  if (SHADOWHOOK_IS_SHARED_MODE)
+    handle = SHADOWHOOK_CALL_PREV(sh_linker_proxy_do_dlopen_l, sh_linker_proxy_do_dlopen_l_t, name, flags,
+                                  extinfo);
+  else
+    handle = sh_linker_orig_do_dlopen_l(name, flags, extinfo);
+
+  if (NULL != handle) sh_linker_post_dlopen(sh_linker_post_dlopen_arg);
+
+  if (SHADOWHOOK_IS_SHARED_MODE) SHADOWHOOK_POP_STACK();
+  return handle;
+}
+
+typedef void *(*sh_linker_proxy_do_dlopen_n_t)(const char *, int, const void *, void *);
+static sh_linker_proxy_do_dlopen_n_t sh_linker_orig_do_dlopen_n;
+static void *sh_linker_proxy_do_dlopen_n(const char *name, int flags, const void *extinfo,
+                                         void *caller_addr) {
+  void *handle;
+  if (SHADOWHOOK_IS_SHARED_MODE)
+    handle = SHADOWHOOK_CALL_PREV(sh_linker_proxy_do_dlopen_n, sh_linker_proxy_do_dlopen_n_t, name, flags,
+                                  extinfo, caller_addr);
+  else
+    handle = sh_linker_orig_do_dlopen_n(name, flags, extinfo, caller_addr);
+
+  if (NULL != handle) sh_linker_post_dlopen(sh_linker_post_dlopen_arg);
+
+  if (SHADOWHOOK_IS_SHARED_MODE) SHADOWHOOK_POP_STACK();
+  return handle;
+}
+
+int sh_linker_hook_dlopen(sh_linker_post_dlopen_t post_dlopen, void *post_dlopen_arg) {
+  static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+  static int result = SHADOWHOOK_ERRNO_MONITOR_DLOPEN;
+
+  if (sh_linker_dlopen_hooked) return result;
+  pthread_mutex_lock(&lock);
+  if (sh_linker_dlopen_hooked) goto end;
+
+  // try hooking-dlopen only once
+  sh_linker_dlopen_hooked = true;
+
+  // do init for SHARED mode
+  if (SHADOWHOOK_IS_SHARED_MODE)
+    if (0 != sh_linker_init()) goto end;
+
+  // save post callback ptr before hooking
+  sh_linker_post_dlopen = post_dlopen;
+  sh_linker_post_dlopen_arg = post_dlopen_arg;
+
+  // hook for dlopen() or do_dlopen()
+  int (*hook)(uintptr_t, uintptr_t, uintptr_t *, size_t *, xdl_info_t *) =
+      SHADOWHOOK_IS_SHARED_MODE ? sh_switch_hook : sh_switch_hook_invisible;
+  int api_level = sh_util_get_api_level();
+  size_t backup_len = 0;
+  int r;
+  if (api_level < __ANDROID_API_L__) {
+    // get & check dlinfo
+    r = sh_linker_get_dlinfo_by_addr((void *)sh_linker_dlopen_addr, &sh_linker_dlopen_dlinfo, NULL, 0, NULL,
+                                     0, false);
+    if (SHADOWHOOK_ERRNO_LINKER_ARCH_MISMATCH == r) result = SHADOWHOOK_ERRNO_LINKER_ARCH_MISMATCH;
+    if (0 != r) goto end;
+
+    // hook
+    r = hook(sh_linker_dlopen_addr, (uintptr_t)sh_linker_proxy_dlopen, (uintptr_t *)&sh_linker_orig_dlopen,
+             &backup_len, &sh_linker_dlopen_dlinfo);
+
+    // record
+    sh_recorder_add_hook(r, true, sh_linker_dlopen_addr, SH_LINKER_BASENAME, "dlopen",
+                         (uintptr_t)sh_linker_proxy_dlopen, backup_len, UINTPTR_MAX,
+                         (uintptr_t)(__builtin_return_address(0)));
+
+    if (0 != r) goto end;
+  } else {
+    // check dlinfo
+    if (!sh_linker_check_arch(&sh_linker_dlopen_dlinfo)) {
+      result = SHADOWHOOK_ERRNO_LINKER_ARCH_MISMATCH;
+      goto end;
+    }
+
+    uintptr_t proxy;
+    uintptr_t *orig;
+    if (api_level >= __ANDROID_API_N__) {
+      proxy = (uintptr_t)sh_linker_proxy_do_dlopen_n;
+      orig = (uintptr_t *)&sh_linker_orig_do_dlopen_n;
+    } else {
+      proxy = (uintptr_t)sh_linker_proxy_do_dlopen_l;
+      orig = (uintptr_t *)&sh_linker_orig_do_dlopen_l;
+    }
+
+    // hook
+    pthread_mutex_lock(sh_linker_g_dl_mutex);
+    r = hook(sh_linker_dlopen_addr, proxy, orig, &backup_len, &sh_linker_dlopen_dlinfo);
+    pthread_mutex_unlock(sh_linker_g_dl_mutex);
+
+    // record
+    sh_recorder_add_hook(r, true, sh_linker_dlopen_addr, SH_LINKER_BASENAME,
+                         sh_linker_dlopen_dlinfo.dli_sname, proxy, backup_len, UINTPTR_MAX,
+                         (uintptr_t)(__builtin_return_address(0)));
+
+    if (0 != r) goto end;
+  }
+
+  // OK
+  result = 0;
+
+end:
+  pthread_mutex_unlock(&lock);
+  SH_LOG_INFO("linker: hook dlopen %s, return: %d", 0 == result ? "OK" : "FAILED", result);
+  return result;
+}
+
+int sh_linker_get_dlinfo_by_addr(void *addr, xdl_info_t *dlinfo, char *lib_name, size_t lib_name_sz,
+                                 char *sym_name, size_t sym_name_sz, bool ignore_symbol_check) {
+  // dladdr()
+  bool crashed = false;
+  void *dlcache = NULL;
+  int r = 0;
+  if (sh_util_get_api_level() >= __ANDROID_API_L__) {
+    r = xdl_addr((void *)addr, dlinfo, &dlcache);
+  } else {
+    SH_SIG_TRY(SIGSEGV, SIGBUS) {
+      r = xdl_addr((void *)addr, dlinfo, &dlcache);
+    }
+    SH_SIG_CATCH() {
+      crashed = true;
+    }
+    SH_SIG_EXIT
+  }
+  SH_LOG_INFO("task: get dlinfo by target addr: target_addr %p, sym_name %s, sym_sz %zu, load_bias %" PRIxPTR
+              ", pathname %s",
+              addr, NULL == dlinfo->dli_sname ? "(NULL)" : dlinfo->dli_sname, dlinfo->dli_ssize,
+              (uintptr_t)dlinfo->dli_fbase, NULL == dlinfo->dli_fname ? "(NULL)" : dlinfo->dli_fname);
+
+  // check error
+  if (crashed) {
+    r = SHADOWHOOK_ERRNO_HOOK_DLADDR_CRASH;
+    goto end;
+  }
+  if (0 == r || NULL == dlinfo->dli_fname) {
+    r = SHADOWHOOK_ERRNO_HOOK_DLINFO;
+    goto end;
+  }
+  if (!sh_linker_check_arch(dlinfo)) {
+    r = SHADOWHOOK_ERRNO_ELF_ARCH_MISMATCH;
+    goto end;
+  }
+
+  if (NULL == dlinfo->dli_sname) {
+    if (ignore_symbol_check) {
+      dlinfo->dli_saddr = addr;
+      dlinfo->dli_sname = "unknown";
+      dlinfo->dli_ssize = 1024;  // big enough
+    } else {
+      const char *matched_dlfcn_name = NULL;
+      if (NULL == (matched_dlfcn_name = sh_linker_match_dlfcn((uintptr_t)addr))) {
+        r = SHADOWHOOK_ERRNO_HOOK_DLINFO;
+        goto end;
+      } else {
+        dlinfo->dli_saddr = addr;
+        dlinfo->dli_sname = matched_dlfcn_name;
+        dlinfo->dli_ssize = 4;  // safe length, only relative jumps are allowed
+        SH_LOG_INFO("task: match dlfcn, target_addr %p, sym_name %s", addr, matched_dlfcn_name);
+      }
+    }
+  }
+  if (0 == dlinfo->dli_ssize) {
+    r = SHADOWHOOK_ERRNO_HOOK_SYMSZ;
+    goto end;
+  }
+
+  if (NULL != lib_name) strlcpy(lib_name, dlinfo->dli_fname, lib_name_sz);
+  if (NULL != sym_name) strlcpy(sym_name, dlinfo->dli_sname, sym_name_sz);
+  r = 0;
+
+end:
+  xdl_addr_clean(&dlcache);
+  return r;
+}
+
+int sh_linker_get_dlinfo_by_sym_name(const char *lib_name, const char *sym_name, xdl_info_t *dlinfo,
+                                     char *real_lib_name, size_t real_lib_name_sz) {
+  // open library
+  bool crashed = false;
+  void *handle = NULL;
+  if (sh_util_get_api_level() >= __ANDROID_API_L__) {
+    handle = xdl_open(lib_name, XDL_DEFAULT);
+  } else {
+    SH_SIG_TRY(SIGSEGV, SIGBUS) {
+      handle = xdl_open(lib_name, XDL_DEFAULT);
+    }
+    SH_SIG_CATCH() {
+      crashed = true;
+    }
+    SH_SIG_EXIT
+  }
+  if (crashed) return SHADOWHOOK_ERRNO_HOOK_DLOPEN_CRASH;
+  if (NULL == handle) return SHADOWHOOK_ERRNO_PENDING;
+
+  // get dlinfo
+  xdl_info(handle, XDL_DI_DLINFO, (void *)dlinfo);
+
+  // check error
+  if (!sh_linker_check_arch(dlinfo)) {
+    xdl_close(handle);
+    return SHADOWHOOK_ERRNO_ELF_ARCH_MISMATCH;
+  }
+
+  // lookup symbol address
+  crashed = false;
+  void *addr = NULL;
+  size_t sym_size = 0;
+  SH_SIG_TRY(SIGSEGV, SIGBUS) {
+    // do xdl_sym() or xdl_dsym() in an dlclosed-ELF will cause a crash
+    addr = xdl_sym(handle, sym_name, &sym_size);
+    if (NULL == addr) addr = xdl_dsym(handle, sym_name, &sym_size);
+  }
+  SH_SIG_CATCH() {
+    crashed = true;
+  }
+  SH_SIG_EXIT
+
+  // close library
+  xdl_close(handle);
+
+  if (crashed) return SHADOWHOOK_ERRNO_HOOK_DLSYM_CRASH;
+  if (NULL == addr) return SHADOWHOOK_ERRNO_HOOK_DLSYM;
+
+  dlinfo->dli_fname = lib_name;
+  dlinfo->dli_sname = sym_name;
+  dlinfo->dli_saddr = addr;
+  dlinfo->dli_ssize = sym_size;
+  if (NULL != real_lib_name) strlcpy(real_lib_name, dlinfo->dli_fname, real_lib_name_sz);
+  return 0;
+}
diff --git a/app/src/main/cpp/shadowhook/sh_linker.h b/app/src/main/cpp/shadowhook/sh_linker.h
new file mode 100644
index 0000000000000000000000000000000000000000..749e0a6d61a732945a85aabc8af3893683d68646
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/sh_linker.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#pragma once
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "xdl.h"
+
+int sh_linker_init(void);
+
+const char *sh_linker_match_dlfcn(uintptr_t target_addr);
+bool sh_linker_need_to_hook_dlopen(uintptr_t target_addr);
+
+typedef void (*sh_linker_post_dlopen_t)(void *arg);
+int sh_linker_hook_dlopen(sh_linker_post_dlopen_t post_dlopen, void *post_dlopen_arg);
+
+int sh_linker_get_dlinfo_by_addr(void *addr, xdl_info_t *dlinfo, char *lib_name, size_t lib_name_sz,
+                                 char *sym_name, size_t sym_name_sz, bool ignore_symbol_check);
+int sh_linker_get_dlinfo_by_sym_name(const char *lib_name, const char *sym_name, xdl_info_t *dlinfo,
+                                     char *real_lib_name, size_t real_lib_name_sz);
diff --git a/app/src/main/cpp/shadowhook/sh_recorder.c b/app/src/main/cpp/shadowhook/sh_recorder.c
new file mode 100644
index 0000000000000000000000000000000000000000..22cf39df74b53224fc013370629e35866264e6f5
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/sh_recorder.c
@@ -0,0 +1,517 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#include "sh_recorder.h"
+
+#include "sh_config.h"
+
+#ifdef SH_CONFIG_OPERATION_RECORDS
+
+#include <inttypes.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include "sh_sig.h"
+#include "sh_util.h"
+#include "shadowhook.h"
+#include "xdl.h"
+
+#define SH_RECORDER_OP_HOOK_SYM_ADDR 0
+#define SH_RECORDER_OP_HOOK_SYM_NAME 1
+#define SH_RECORDER_OP_UNHOOK        2
+
+#define SH_RECORDER_LIB_NAME_MAX 512
+#define SH_RECORDER_SYM_NAME_MAX 1024
+
+#define SH_RECORDER_STRINGS_BUF_EXPAND_STEP (1024 * 16)
+#define SH_RECORDER_STRINGS_BUF_MAX         (1024 * 128)
+#define SH_RECORDER_RECORDS_BUF_EXPAND_STEP (1024 * 32)
+#define SH_RECORDER_RECORDS_BUF_MAX         (1024 * 384)
+#define SH_RECORDER_OUTPUT_BUF_EXPAND_STEP  (1024 * 128)
+#define SH_RECORDER_OUTPUT_BUF_MAX          (1024 * 1024)
+
+static bool sh_recorder_recordable = false;
+
+bool sh_recorder_get_recordable(void) {
+  return sh_recorder_recordable;
+}
+
+void sh_recorder_set_recordable(bool recordable) {
+  sh_recorder_recordable = recordable;
+}
+
+typedef struct {
+  void *ptr;
+  size_t cap;
+  size_t sz;
+  pthread_mutex_t lock;
+} sh_recorder_buf_t;
+
+static int sh_recorder_buf_append(sh_recorder_buf_t *buf, size_t step, size_t max, const void *header,
+                                  size_t header_sz, const void *body, size_t body_sz) {
+  size_t needs = (header_sz + (NULL != body ? body_sz : 0));
+  if (needs > step) return -1;
+
+  if (buf->cap - buf->sz < needs) {
+    size_t new_cap = buf->cap + step;
+    if (new_cap > max) return -1;
+    void *new_ptr = realloc(buf->ptr, new_cap);
+    if (NULL == new_ptr) return -1;
+    buf->ptr = new_ptr;
+    buf->cap = new_cap;
+  }
+
+  memcpy((void *)((uintptr_t)buf->ptr + buf->sz), header, header_sz);
+  if (NULL != body) memcpy((void *)((uintptr_t)buf->ptr + buf->sz + header_sz), body, body_sz);
+  buf->sz += needs;
+  return 0;
+}
+
+static void sh_recorder_buf_free(sh_recorder_buf_t *buf) {
+  if (NULL != buf->ptr) {
+    free(buf->ptr);
+    buf->ptr = NULL;
+  }
+}
+
+static sh_recorder_buf_t sh_recorder_strings = {NULL, 0, 0, PTHREAD_MUTEX_INITIALIZER};
+static sh_recorder_buf_t sh_recorder_records = {NULL, 0, 0, PTHREAD_MUTEX_INITIALIZER};
+static bool sh_recorder_error = false;
+
+typedef struct {
+  uint16_t str_len;  // body length, in order to speed up the search
+} __attribute__((packed)) sh_recorder_str_header_t;
+// +body: string, including the terminating null byte ('\0')
+
+typedef struct {
+  uint64_t op : 8;
+  uint64_t error_number : 8;
+  uint64_t ts_ms : 48;
+  uintptr_t stub;
+  uint16_t caller_lib_name_idx;
+  uint8_t backup_len;
+  uint16_t lib_name_idx;
+  uint16_t sym_name_idx;
+  uintptr_t sym_addr;
+  uintptr_t new_addr;
+} __attribute__((packed)) sh_recorder_record_hook_header_t;
+// no body
+
+typedef struct {
+  uint64_t op : 8;
+  uint64_t error_number : 8;
+  uint64_t ts_ms : 48;
+  uintptr_t stub;
+  uint16_t caller_lib_name_idx;
+} __attribute__((packed)) sh_recorder_record_unhook_header_t;
+// no body
+
+static int sh_recorder_add_str(const char *str, size_t str_len, uint16_t *str_idx) {
+  uint16_t idx = 0;
+  bool ok = false;
+
+  pthread_mutex_lock(&sh_recorder_strings.lock);
+
+  // find in existing strings
+  size_t i = 0;
+  while (i < sh_recorder_strings.sz) {
+    sh_recorder_str_header_t *header = (sh_recorder_str_header_t *)((uintptr_t)sh_recorder_strings.ptr + i);
+    if (header->str_len == str_len) {
+      void *tmp = (void *)((uintptr_t)sh_recorder_strings.ptr + i + sizeof(header->str_len));
+      if (0 == memcmp(tmp, str, str_len)) {
+        *str_idx = idx;
+        ok = true;
+        break;  // OK
+      }
+    }
+    i += (sizeof(sh_recorder_str_header_t) + header->str_len + 1);
+    idx++;
+    if (idx == UINT16_MAX) break;  // failed
+  }
+
+  // insert a new string
+  if (!ok && idx < UINT16_MAX) {
+    // append new string
+    sh_recorder_str_header_t header = {(uint16_t)str_len};
+    if (0 == sh_recorder_buf_append(&sh_recorder_strings, SH_RECORDER_STRINGS_BUF_EXPAND_STEP,
+                                    SH_RECORDER_STRINGS_BUF_MAX, &header, sizeof(header), str, str_len + 1)) {
+      *str_idx = idx;
+      ok = true;  // OK
+    }
+  }
+
+  pthread_mutex_unlock(&sh_recorder_strings.lock);
+
+  return ok ? 0 : -1;
+}
+
+static char *sh_recorder_find_str(uint16_t idx) {
+  uint16_t cur_idx = 0;
+
+  size_t i = 0;
+  while (i < sh_recorder_strings.sz && cur_idx < idx) {
+    sh_recorder_str_header_t *header = (sh_recorder_str_header_t *)((uintptr_t)sh_recorder_strings.ptr + i);
+    i += (sizeof(sh_recorder_str_header_t) + header->str_len + 1);
+    cur_idx++;
+  }
+  if (cur_idx != idx) return "error";
+
+  sh_recorder_str_header_t *header = (sh_recorder_str_header_t *)((uintptr_t)sh_recorder_strings.ptr + i);
+  return (char *)((uintptr_t)header + sizeof(sh_recorder_str_header_t));
+}
+
+static long sh_recorder_tz = LONG_MAX;
+
+static uint64_t sh_recorder_get_timestamp_ms(void) {
+  struct timeval tv;
+  gettimeofday(&tv, NULL);
+
+  if (LONG_MAX == sh_recorder_tz) {
+    // localtime_r() will call getenv() without lock protection,
+    // and will crash when encountering concurrent setenv() calls.
+    // We really encountered.
+    sh_recorder_tz = 0;
+    //    struct tm tm;
+    //    if (NULL != localtime_r((time_t *)(&(tv.tv_sec)), &tm)) sh_recorder_tz = tm.tm_gmtoff;
+  }
+
+  return (uint64_t)tv.tv_sec * 1000 + (uint64_t)tv.tv_usec / 1000;
+}
+
+static size_t sh_recorder_format_timestamp_ms(uint64_t ts_ms, char *buf, size_t buf_len) {
+  time_t sec = (time_t)(ts_ms / 1000);
+  time_t msec = (time_t)(ts_ms % 1000);
+
+  struct tm tm;
+  sh_util_localtime_r(&sec, sh_recorder_tz, &tm);
+
+  return sh_util_snprintf(buf, buf_len, "%04d-%02d-%02dT%02d:%02d:%02d.%03ld%c%02ld:%02ld,",
+                          tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
+                          msec, sh_recorder_tz < 0 ? '-' : '+', labs(sh_recorder_tz / 3600),
+                          labs(sh_recorder_tz % 3600));
+}
+
+static const char *sh_recorder_get_base_name(const char *lib_name) {
+  const char *p = strrchr(lib_name, '/');
+  if (NULL != p && '\0' != *(p + 1))
+    return p + 1;
+  else
+    return lib_name;
+}
+
+static int sh_recorder_get_base_name_by_addr_iterator(struct dl_phdr_info *info, size_t size, void *arg) {
+  (void)size;
+
+  uintptr_t *pkg = (uintptr_t *)arg;
+  uintptr_t addr = *pkg++;
+  char *base_name = (char *)*pkg++;
+  size_t base_name_sz = (size_t)*pkg;
+
+  for (size_t i = 0; i < info->dlpi_phnum; i++) {
+    const ElfW(Phdr) *phdr = &(info->dlpi_phdr[i]);
+    if (PT_LOAD != phdr->p_type) continue;
+    if (addr < (uintptr_t)(info->dlpi_addr + phdr->p_vaddr) ||
+        addr >= (uintptr_t)(info->dlpi_addr + phdr->p_vaddr + phdr->p_memsz))
+      continue;
+
+    // get lib_name from path_name
+    const char *p;
+    if (NULL == info->dlpi_name || '\0' == info->dlpi_name[0])
+      p = "unknown";
+    else {
+      p = strrchr(info->dlpi_name, '/');
+      if (NULL == p || '\0' == *(p + 1))
+        p = info->dlpi_name;
+      else
+        p++;
+    }
+
+    strlcpy(base_name, p, base_name_sz);
+    return 1;  // OK
+  }
+
+  return 0;  // continue
+}
+
+static void sh_recorder_get_base_name_by_addr(uintptr_t addr, char *base_name, size_t base_name_sz) {
+  base_name[0] = '\0';
+  uintptr_t pkg[3] = {addr, (uintptr_t)base_name, (uintptr_t)base_name_sz};
+
+  if (sh_util_get_api_level() >= __ANDROID_API_L__) {
+    xdl_iterate_phdr(sh_recorder_get_base_name_by_addr_iterator, pkg, XDL_DEFAULT);
+  } else {
+    SH_SIG_TRY(SIGSEGV, SIGBUS) {
+      xdl_iterate_phdr(sh_recorder_get_base_name_by_addr_iterator, pkg, XDL_DEFAULT);
+    }
+    SH_SIG_EXIT
+  }
+
+  if ('\0' == base_name[0]) strlcpy(base_name, "unknown", base_name_sz);
+}
+
+int sh_recorder_add_hook(int error_number, bool is_hook_sym_addr, uintptr_t sym_addr, const char *lib_name,
+                         const char *sym_name, uintptr_t new_addr, size_t backup_len, uintptr_t stub,
+                         uintptr_t caller_addr) {
+  if (!sh_recorder_recordable) return 0;
+  if (sh_recorder_error) return -1;
+
+  // lib_name
+  if (NULL == lib_name) return -1;
+  lib_name = sh_recorder_get_base_name(lib_name);
+  size_t lib_name_len = strlen(lib_name);
+  if (0 == lib_name_len || lib_name_len > SH_RECORDER_LIB_NAME_MAX) return -1;
+
+  // sym_name
+  if (NULL == sym_name) return -1;
+  size_t sym_name_len = strlen(sym_name);
+  if (0 == sym_name_len || sym_name_len > SH_RECORDER_SYM_NAME_MAX) return -1;
+
+  // caller_lib_name
+  char caller_lib_name[SH_RECORDER_LIB_NAME_MAX];
+  sh_recorder_get_base_name_by_addr(caller_addr, caller_lib_name, sizeof(caller_lib_name));
+  size_t caller_lib_name_len = strlen(caller_lib_name);
+
+  // add strings to strings-pool
+  uint16_t lib_name_idx, sym_name_idx, caller_lib_name_idx;
+  if (0 != sh_recorder_add_str(lib_name, lib_name_len, &lib_name_idx)) goto err;
+  if (0 != sh_recorder_add_str(sym_name, sym_name_len, &sym_name_idx)) goto err;
+  if (0 != sh_recorder_add_str(caller_lib_name, caller_lib_name_len, &caller_lib_name_idx)) goto err;
+
+  // append new hook record
+  sh_recorder_record_hook_header_t header = {
+      is_hook_sym_addr ? SH_RECORDER_OP_HOOK_SYM_ADDR : SH_RECORDER_OP_HOOK_SYM_NAME,
+      (uint8_t)error_number,
+      sh_recorder_get_timestamp_ms(),
+      stub,
+      caller_lib_name_idx,
+      (uint8_t)backup_len,
+      lib_name_idx,
+      sym_name_idx,
+      sym_addr,
+      new_addr};
+  pthread_mutex_lock(&sh_recorder_records.lock);
+  int r = sh_recorder_buf_append(&sh_recorder_records, SH_RECORDER_RECORDS_BUF_EXPAND_STEP,
+                                 SH_RECORDER_RECORDS_BUF_MAX, &header, sizeof(header), NULL, 0);
+  pthread_mutex_unlock(&sh_recorder_records.lock);
+  if (0 != r) goto err;
+
+  return 0;
+
+err:
+  sh_recorder_error = true;
+  return -1;
+}
+
+int sh_recorder_add_unhook(int error_number, uintptr_t stub, uintptr_t caller_addr) {
+  if (!sh_recorder_recordable) return 0;
+  if (sh_recorder_error) return -1;
+
+  char caller_lib_name[SH_RECORDER_LIB_NAME_MAX];
+  sh_recorder_get_base_name_by_addr(caller_addr, caller_lib_name, sizeof(caller_lib_name));
+  size_t caller_lib_name_len = strlen(caller_lib_name);
+
+  uint16_t caller_lib_name_idx;
+  if (0 != sh_recorder_add_str(caller_lib_name, caller_lib_name_len, &caller_lib_name_idx)) goto err;
+
+  sh_recorder_record_unhook_header_t header = {SH_RECORDER_OP_UNHOOK, (uint8_t)error_number,
+                                               sh_recorder_get_timestamp_ms(), stub, caller_lib_name_idx};
+  pthread_mutex_lock(&sh_recorder_records.lock);
+  int r = sh_recorder_buf_append(&sh_recorder_records, SH_RECORDER_RECORDS_BUF_EXPAND_STEP,
+                                 SH_RECORDER_RECORDS_BUF_MAX, &header, sizeof(header), NULL, 0);
+  pthread_mutex_unlock(&sh_recorder_records.lock);
+  if (0 != r) goto err;
+
+  return 0;
+
+err:
+  sh_recorder_error = true;
+  return -1;
+}
+
+static const char *sh_recorder_get_op_name(uint8_t op) {
+  switch (op) {
+    case SH_RECORDER_OP_HOOK_SYM_ADDR:
+      return "hook_sym_addr";
+    case SH_RECORDER_OP_HOOK_SYM_NAME:
+      return "hook_sym_name";
+    case SH_RECORDER_OP_UNHOOK:
+      return "unhook";
+    default:
+      return "error";
+  }
+}
+
+static void sh_recorder_output(char **str, int fd, uint32_t item_flags) {
+  if (NULL == sh_recorder_records.ptr || 0 == sh_recorder_records.sz) return;
+
+  sh_recorder_buf_t output = {NULL, 0, 0, PTHREAD_MUTEX_INITIALIZER};
+
+  pthread_mutex_lock(&sh_recorder_records.lock);
+  pthread_mutex_lock(&sh_recorder_strings.lock);
+
+  char line[SH_RECORDER_LIB_NAME_MAX * 2 + SH_RECORDER_SYM_NAME_MAX + 256];
+  size_t line_sz;
+  size_t i = 0;
+  while (i < sh_recorder_records.sz) {
+    line_sz = 0;
+    sh_recorder_record_hook_header_t *header =
+        (sh_recorder_record_hook_header_t *)((uintptr_t)sh_recorder_records.ptr + i);
+
+    if (item_flags & SHADOWHOOK_RECORD_ITEM_TIMESTAMP)
+      line_sz += sh_recorder_format_timestamp_ms(header->ts_ms, line + line_sz, sizeof(line) - line_sz);
+    if (item_flags & SHADOWHOOK_RECORD_ITEM_CALLER_LIB_NAME)
+      line_sz += sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "%s,",
+                                  sh_recorder_find_str(header->caller_lib_name_idx));
+    if (item_flags & SHADOWHOOK_RECORD_ITEM_OP)
+      line_sz += sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "%s,",
+                                  sh_recorder_get_op_name(header->op));
+    if ((item_flags & SHADOWHOOK_RECORD_ITEM_LIB_NAME) && header->op != SH_RECORDER_OP_UNHOOK)
+      line_sz += sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "%s,",
+                                  sh_recorder_find_str(header->lib_name_idx));
+    if ((item_flags & SHADOWHOOK_RECORD_ITEM_SYM_NAME) && header->op != SH_RECORDER_OP_UNHOOK)
+      line_sz += sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "%s,",
+                                  sh_recorder_find_str(header->sym_name_idx));
+    if ((item_flags & SHADOWHOOK_RECORD_ITEM_SYM_ADDR) && header->op != SH_RECORDER_OP_UNHOOK)
+      line_sz += sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "%" PRIxPTR ",", header->sym_addr);
+    if ((item_flags & SHADOWHOOK_RECORD_ITEM_NEW_ADDR) && header->op != SH_RECORDER_OP_UNHOOK)
+      line_sz += sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "%" PRIxPTR ",", header->new_addr);
+    if ((item_flags & SHADOWHOOK_RECORD_ITEM_BACKUP_LEN) && header->op != SH_RECORDER_OP_UNHOOK)
+      line_sz += sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "%" PRIu8 ",", header->backup_len);
+    if (item_flags & SHADOWHOOK_RECORD_ITEM_ERRNO)
+      line_sz +=
+          sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "%" PRIu8 ",", header->error_number);
+    if (item_flags & SHADOWHOOK_RECORD_ITEM_STUB)
+      line_sz += sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "%" PRIxPTR ",", header->stub);
+    line[line_sz - 1] = '\n';
+
+    if (NULL != str) {
+      // append to string
+      if (0 != sh_recorder_buf_append(&output, SH_RECORDER_OUTPUT_BUF_EXPAND_STEP, SH_RECORDER_OUTPUT_BUF_MAX,
+                                      line, line_sz, NULL, 0)) {
+        sh_recorder_buf_free(&output);
+        break;  // failed
+      }
+    } else {
+      // write to FD
+      if (0 != sh_util_write(fd, line, line_sz)) break;  // failed
+    }
+
+    i += (SH_RECORDER_OP_UNHOOK == header->op ? sizeof(sh_recorder_record_unhook_header_t)
+                                              : sizeof(sh_recorder_record_hook_header_t));
+  }
+
+  pthread_mutex_unlock(&sh_recorder_strings.lock);
+  pthread_mutex_unlock(&sh_recorder_records.lock);
+
+  // error message
+  if (sh_recorder_error) {
+    line_sz = 0;
+
+    if (item_flags & SHADOWHOOK_RECORD_ITEM_TIMESTAMP)
+      line_sz += sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "9999-99-99T00:00:00.000+00:00,");
+    if (item_flags & SHADOWHOOK_RECORD_ITEM_CALLER_LIB_NAME)
+      line_sz += sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "error,");
+    if (item_flags & SHADOWHOOK_RECORD_ITEM_OP)
+      line_sz += sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "error,");
+
+    if (0 == line_sz) line_sz = sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "error,");
+
+    line[line_sz - 1] = '\n';
+
+    if (NULL != str) {
+      // append to string
+      if (0 != sh_recorder_buf_append(&output, SH_RECORDER_OUTPUT_BUF_EXPAND_STEP, SH_RECORDER_OUTPUT_BUF_MAX,
+                                      line, line_sz, NULL, 0)) {
+        sh_recorder_buf_free(&output);
+        return;  // failed
+      }
+    } else {
+      // write to FD
+      if (0 != sh_util_write(fd, line, line_sz)) return;  // failed
+    }
+  }
+
+  // return string
+  if (NULL != str) {
+    if (0 != sh_recorder_buf_append(&output, SH_RECORDER_OUTPUT_BUF_EXPAND_STEP, SH_RECORDER_OUTPUT_BUF_MAX,
+                                    "", 1, NULL, 0)) {
+      sh_recorder_buf_free(&output);
+      return;  // failed
+    }
+    *str = output.ptr;
+  }
+}
+
+char *sh_recorder_get(uint32_t item_flags) {
+  if (!sh_recorder_recordable) return NULL;
+  if (0 == (item_flags & SHADOWHOOK_RECORD_ITEM_ALL)) return NULL;
+
+  char *str = NULL;
+  sh_recorder_output(&str, -1, item_flags);
+  return str;
+}
+
+void sh_recorder_dump(int fd, uint32_t item_flags) {
+  if (!sh_recorder_recordable) return;
+  if (0 == (item_flags & SHADOWHOOK_RECORD_ITEM_ALL)) return;
+  if (fd < 0) return;
+  sh_recorder_output(NULL, fd, item_flags);
+}
+
+#else
+
+bool sh_recorder_get_recordable(void) {
+  return false;
+}
+
+void sh_recorder_set_recordable(bool recordable) {
+  (void)recordable;
+}
+
+int sh_recorder_add_hook(int error_number, bool is_hook_sym_addr, uintptr_t sym_addr, const char *lib_name,
+                         const char *sym_name, uintptr_t new_addr, size_t backup_len, uintptr_t stub,
+                         uintptr_t caller_addr) {
+  (void)error_number, (void)is_hook_sym_addr, (void)sym_addr, (void)lib_name, (void)sym_name, (void)new_addr,
+      (void)backup_len, (void)stub, (void)caller_addr;
+  return 0;
+}
+
+int sh_recorder_add_unhook(int error_number, uintptr_t stub, uintptr_t caller_addr) {
+  (void)error_number, (void)stub, (void)caller_addr;
+  return 0;
+}
+
+char *sh_recorder_get(uint32_t item_flags) {
+  (void)item_flags;
+  return NULL;
+}
+
+void sh_recorder_dump(int fd, uint32_t item_flags) {
+  (void)fd, (void)item_flags;
+}
+
+#endif
diff --git a/app/src/main/cpp/shadowhook/sh_recorder.h b/app/src/main/cpp/shadowhook/sh_recorder.h
new file mode 100644
index 0000000000000000000000000000000000000000..91ce8435476fac0df3599fd88f23c3eeb54ad7be
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/sh_recorder.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#pragma once
+#include <stdbool.h>
+#include <stdint.h>
+
+bool sh_recorder_get_recordable(void);
+void sh_recorder_set_recordable(bool recordable);
+
+int sh_recorder_add_hook(int error_number, bool is_hook_sym_addr, uintptr_t sym_addr, const char *lib_name,
+                         const char *sym_name, uintptr_t new_addr, size_t backup_len, uintptr_t stub,
+                         uintptr_t caller_addr);
+int sh_recorder_add_unhook(int error_number, uintptr_t stub, uintptr_t caller_addr);
+
+char *sh_recorder_get(uint32_t item_flags);
+void sh_recorder_dump(int fd, uint32_t item_flags);
diff --git a/app/src/main/cpp/shadowhook/sh_safe.c b/app/src/main/cpp/shadowhook/sh_safe.c
new file mode 100644
index 0000000000000000000000000000000000000000..eacc14dfa10b7e5c0b17e66bf72980fd5d83b795
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/sh_safe.c
@@ -0,0 +1,133 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#include "sh_safe.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "sh_util.h"
+#include "xdl.h"
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wreserved-id-macro"
+#pragma clang diagnostic ignored "-Wvariadic-macros"
+#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
+#pragma clang diagnostic ignored "-Wsign-conversion"
+#pragma clang diagnostic ignored "-Wpacked"
+#pragma clang diagnostic ignored "-Wshorten-64-to-32"
+#include "linux_syscall_support.h"
+#pragma clang diagnostic pop
+
+#define SH_SAFE_IDX_PTHREAD_GETSPECIFIC 0
+#define SH_SAFE_IDX_PTHREAD_SETSPECIFIC 1
+#define SH_SAFE_IDX_ABORT               2
+
+#define SH_SAFE_IDX_SZ 3
+typedef struct {
+  uintptr_t target_addr;
+  uintptr_t orig_addr;
+} sh_safe_addr_t;
+static sh_safe_addr_t sh_safe_addrs[SH_SAFE_IDX_SZ];
+static int sh_safe_api_level;
+
+static int sh_safe_init_func(void *handle, const char *symbol, size_t idx) {
+  sh_safe_addrs[idx].target_addr = (uintptr_t)(xdl_sym(handle, symbol, NULL));
+  if (__predict_false(0 == sh_safe_addrs[idx].target_addr)) return -1;
+  sh_safe_addrs[idx].orig_addr = 0;
+  return 0;
+}
+
+int sh_safe_init(void) {
+  sh_safe_api_level = sh_util_get_api_level();
+
+  void *handle = xdl_open("libc.so", XDL_DEFAULT);
+  if (NULL == handle) return -1;
+
+  int r = -1;
+  if (__predict_false(0 != sh_safe_init_func(handle, "pthread_getspecific", SH_SAFE_IDX_PTHREAD_GETSPECIFIC)))
+    goto end;
+  if (__predict_false(0 != sh_safe_init_func(handle, "pthread_setspecific", SH_SAFE_IDX_PTHREAD_SETSPECIFIC)))
+    goto end;
+  if (__predict_false(0 != sh_safe_init_func(handle, "abort", SH_SAFE_IDX_ABORT))) goto end;
+  r = 0;
+
+end:
+  xdl_close(handle);
+  return r;
+}
+
+uintptr_t *sh_safe_get_orig_addr_addr(uintptr_t target_addr) {
+  for (size_t i = 0; i < SH_SAFE_IDX_SZ; i++) {
+    if (sh_safe_addrs[i].target_addr == target_addr) {
+      return &sh_safe_addrs[i].orig_addr;
+    }
+  }
+  return NULL;
+}
+
+static uintptr_t sh_safe_get_orig_addr(size_t idx) {
+  sh_safe_addr_t *addr = &sh_safe_addrs[idx];
+  return 0 != addr->orig_addr ? addr->orig_addr : addr->target_addr;
+}
+
+void *sh_safe_pthread_getspecific(pthread_key_t key) {
+  uintptr_t addr = sh_safe_get_orig_addr(SH_SAFE_IDX_PTHREAD_GETSPECIFIC);
+  return ((void *(*)(pthread_key_t))addr)(key);
+}
+
+int sh_safe_pthread_setspecific(pthread_key_t key, const void *value) {
+  if (sh_safe_api_level >= __ANDROID_API_M__) {
+    uintptr_t addr = sh_safe_get_orig_addr(SH_SAFE_IDX_PTHREAD_SETSPECIFIC);
+    return ((int (*)(pthread_key_t, const void *))addr)(key, value);
+  } else {
+    // Before Android M, pthread_setspecific() will call pthread_mutex_lock() and
+    // pthread_mutex_unlock(). So if we use pthread_setspecific() in hub's trampo,
+    // we will NOT be able to hook pthread_mutex_lock() and pthread_mutex_unlock().
+    void **tls;
+#if defined(__aarch64__)
+    __asm__("mrs %0, tpidr_el0" : "=r"(tls));
+#elif defined(__arm__)
+    __asm__("mrc p15, 0, %0, c13, c0, 3" : "=r"(tls));
+#endif
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wcast-qual"
+    tls[key] = (void *)value;
+#pragma clang diagnostic pop
+    return 0;
+  }
+}
+
+void sh_safe_abort(void) {
+  uintptr_t addr = sh_safe_get_orig_addr(SH_SAFE_IDX_ABORT);
+  ((void (*)(void))addr)();
+}
+
+void *sh_safe_mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) {
+  return sys_mmap(addr, length, prot, flags, fd, offset);
+}
+
+int sh_safe_prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4,
+                  unsigned long arg5) {
+  return sys_prctl(option, arg2, arg3, arg4, arg5);
+}
diff --git a/app/src/main/cpp/shadowhook/sh_safe.h b/app/src/main/cpp/shadowhook/sh_safe.h
new file mode 100644
index 0000000000000000000000000000000000000000..5f353bd8687dd0e199c371230d35859f02e78e34
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/sh_safe.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#pragma once
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+int sh_safe_init(void);
+uintptr_t *sh_safe_get_orig_addr_addr(uintptr_t target_addr);
+
+void *sh_safe_pthread_getspecific(pthread_key_t key);
+int sh_safe_pthread_setspecific(pthread_key_t key, const void *value);
+void sh_safe_abort(void);
+
+void *sh_safe_mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
+int sh_safe_prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);
diff --git a/app/src/main/cpp/shadowhook/sh_switch.c b/app/src/main/cpp/shadowhook/sh_switch.c
new file mode 100644
index 0000000000000000000000000000000000000000..0eb7ba9eee355ab31fbae321f8c0fcf3deab5ac2
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/sh_switch.c
@@ -0,0 +1,343 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#include "sh_switch.h"
+
+#include <inttypes.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "sh_config.h"
+#include "sh_errno.h"
+#include "sh_hub.h"
+#include "sh_inst.h"
+#include "sh_linker.h"
+#include "sh_log.h"
+#include "sh_safe.h"
+#include "sh_sig.h"
+#include "sh_util.h"
+#include "shadowhook.h"
+#include "tree.h"
+#include "xdl.h"
+
+// switch
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+typedef struct sh_switch {
+  sh_inst_t inst;  // align 16
+  uintptr_t target_addr;
+  sh_hub_t *hub;
+  RB_ENTRY(sh_switch) link;
+} sh_switch_t;
+#pragma clang diagnostic pop
+
+// switch tree
+static __inline__ int sh_switch_cmp(sh_switch_t *a, sh_switch_t *b) {
+  if (a->target_addr == b->target_addr)
+    return 0;
+  else
+    return a->target_addr > b->target_addr ? 1 : -1;
+}
+typedef RB_HEAD(sh_switch_tree, sh_switch) sh_switch_tree_t;
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-function"
+RB_GENERATE_STATIC(sh_switch_tree, sh_switch, link, sh_switch_cmp)
+#pragma clang diagnostic pop
+
+// switch tree object
+static sh_switch_tree_t sh_switches = RB_INITIALIZER(&sh_switches);
+static pthread_rwlock_t sh_switches_lock = PTHREAD_RWLOCK_INITIALIZER;
+
+static sh_switch_t *sh_switch_find(uintptr_t target_addr) {
+  sh_switch_t key = {.target_addr = target_addr};
+
+  pthread_rwlock_rdlock(&sh_switches_lock);
+  sh_switch_t *self = RB_FIND(sh_switch_tree, &sh_switches, &key);
+  pthread_rwlock_unlock(&sh_switches_lock);
+
+  return self;
+}
+
+static int sh_switch_create(sh_switch_t **self, uintptr_t target_addr, uintptr_t *hub_trampo) {
+  *self = memalign(16, sizeof(sh_switch_t));
+  if (NULL == *self) return SHADOWHOOK_ERRNO_OOM;
+
+  memset(&(*self)->inst, 0, sizeof((*self)->inst));
+  (*self)->target_addr = target_addr;
+  (*self)->hub = NULL;
+
+  if (NULL != hub_trampo) {
+    if (NULL == ((*self)->hub = sh_hub_create(target_addr, hub_trampo))) {
+      free(self);
+      return SHADOWHOOK_ERRNO_HUB_CREAT;
+    }
+  }
+
+  return 0;
+}
+
+static void sh_switch_destroy(sh_switch_t *self, bool hub_with_delay) {
+  if (NULL != self->hub) sh_hub_destroy(self->hub, hub_with_delay);
+  free(self);
+}
+
+static void sh_switch_dump_enter(sh_switch_t *self) {
+#ifdef SH_CONFIG_DEBUG
+  char buf[1024];
+  size_t len = 0;
+  size_t zero = 0;
+  for (size_t i = 0; i < 128; i++) {
+    uint16_t inst = ((uint16_t *)(self->inst.enter_addr))[i];
+    zero = (0 == inst ? zero + 1 : 0);
+    if (zero > 4) break;
+    len += (size_t)snprintf(buf + len, sizeof(buf) - len, "%04" PRIx16 " ", inst);
+  }
+  SH_LOG_DEBUG("switch: enter < %s>", buf);
+#else
+  (void)self;
+#endif
+}
+
+static int sh_switch_hook_unique(uintptr_t target_addr, uintptr_t new_addr, uintptr_t *orig_addr,
+                                 size_t *backup_len, xdl_info_t *dlinfo) {
+  sh_switch_t *self = sh_switch_find(target_addr);
+  if (NULL != self) return SHADOWHOOK_ERRNO_HOOK_DUP;
+
+  // alloc new switch
+  int r;
+  if (0 != (r = sh_switch_create(&self, target_addr, NULL))) return r;
+
+  sh_switch_t *useless = NULL;
+  pthread_rwlock_wrlock(&sh_switches_lock);  // SYNC - start
+
+  // insert new switch to switch-tree
+  if (NULL != RB_INSERT(sh_switch_tree, &sh_switches, self)) {
+    useless = self;
+    r = SHADOWHOOK_ERRNO_HOOK_DUP;
+    goto end;
+  }
+
+  // do hook
+  if (0 != (r = sh_inst_hook(&self->inst, target_addr, dlinfo, new_addr, orig_addr, NULL))) {
+    RB_REMOVE(sh_switch_tree, &sh_switches, self);
+    useless = self;
+    goto end;
+  }
+  *backup_len = self->inst.backup_len;
+  sh_switch_dump_enter(self);
+
+end:
+  pthread_rwlock_unlock(&sh_switches_lock);  // SYNC - end
+  if (NULL != useless) sh_switch_destroy(useless, false);
+  return r;
+}
+
+static int sh_switch_hook_shared(uintptr_t target_addr, uintptr_t new_addr, uintptr_t *orig_addr,
+                                 size_t *backup_len, xdl_info_t *dlinfo) {
+  int r;
+
+  pthread_rwlock_rdlock(&sh_switches_lock);  // SYNC(read) - start
+  sh_switch_t key = {.target_addr = target_addr};
+  sh_switch_t *self = RB_FIND(sh_switch_tree, &sh_switches, &key);
+  if (NULL != self)  // already exists
+  {
+    // add an new proxy to hub
+    if (NULL != orig_addr) *orig_addr = sh_hub_get_orig_addr(self->hub);
+    r = sh_hub_add_proxy(self->hub, new_addr);
+    pthread_rwlock_unlock(&sh_switches_lock);  // SYNC(read) - end
+
+    *backup_len = self->inst.backup_len;
+    return r;
+  }
+  pthread_rwlock_unlock(&sh_switches_lock);  // SYNC(read) - end
+
+  // first hook for this target_addr
+
+  // alloc new switch
+  uintptr_t hub_trampo;
+  if (0 != (r = sh_switch_create(&self, target_addr, &hub_trampo))) return r;
+
+  sh_switch_t *useless = NULL;
+  pthread_rwlock_wrlock(&sh_switches_lock);  // SYNC - start
+
+  // insert new switch to switch-tree
+  sh_switch_t *exists;
+  if (NULL != (exists = RB_INSERT(sh_switch_tree, &sh_switches, self))) {
+    // already exists
+    useless = self;
+    if (NULL != orig_addr) *orig_addr = sh_hub_get_orig_addr(exists->hub);
+    r = sh_hub_add_proxy(exists->hub, new_addr);
+    *backup_len = exists->inst.backup_len;
+  } else {
+    // do hook
+    uintptr_t *safe_orig_addr_addr = sh_safe_get_orig_addr_addr(target_addr);
+    if (0 != (r = sh_inst_hook(&self->inst, target_addr, dlinfo, hub_trampo,
+                               sh_hub_get_orig_addr_addr(self->hub), safe_orig_addr_addr))) {
+      RB_REMOVE(sh_switch_tree, &sh_switches, self);
+      useless = self;
+      goto end;
+    }
+    *backup_len = self->inst.backup_len;
+    sh_switch_dump_enter(self);
+
+    // return original-address
+    if (NULL != orig_addr) *orig_addr = sh_hub_get_orig_addr(self->hub);
+
+    // add proxy to hub
+    if (0 != (r = sh_hub_add_proxy(self->hub, new_addr))) {
+      sh_inst_unhook(&self->inst, target_addr);
+      *backup_len = 0;
+      RB_REMOVE(sh_switch_tree, &sh_switches, self);
+      useless = self;
+      goto end;
+    }
+  }
+
+end:
+  pthread_rwlock_unlock(&sh_switches_lock);  // SYNC - end
+  if (NULL != useless) sh_switch_destroy(useless, false);
+
+  return r;
+}
+
+int sh_switch_hook(uintptr_t target_addr, uintptr_t new_addr, uintptr_t *orig_addr, size_t *backup_len,
+                   xdl_info_t *dlinfo) {
+  int r;
+  if (SHADOWHOOK_IS_UNIQUE_MODE)
+    r = sh_switch_hook_unique(target_addr, new_addr, orig_addr, backup_len, dlinfo);
+  else
+    r = sh_switch_hook_shared(target_addr, new_addr, orig_addr, backup_len, dlinfo);
+
+  if (0 == r)
+    SH_LOG_INFO("switch: hook in %s mode OK: target_addr %" PRIxPTR ", new_addr %" PRIxPTR,
+                SHADOWHOOK_IS_UNIQUE_MODE ? "UNIQUE" : "SHARED", target_addr, new_addr);
+
+  return r;
+}
+
+static int sh_switch_hook_unique_invisible(uintptr_t target_addr, uintptr_t new_addr, uintptr_t *orig_addr,
+                                           size_t *backup_len, xdl_info_t *dlinfo) {
+  pthread_rwlock_wrlock(&sh_switches_lock);  // SYNC - start
+
+  // do hook
+  sh_inst_t inst;
+  int r = sh_inst_hook(&inst, target_addr, dlinfo, new_addr, orig_addr, NULL);
+
+  pthread_rwlock_unlock(&sh_switches_lock);  // SYNC - end
+
+  *backup_len = inst.backup_len;
+  return r;
+}
+
+int sh_switch_hook_invisible(uintptr_t target_addr, uintptr_t new_addr, uintptr_t *orig_addr,
+                             size_t *backup_len, xdl_info_t *dlinfo) {
+  int r;
+  if (SHADOWHOOK_IS_UNIQUE_MODE)
+    r = sh_switch_hook_unique_invisible(target_addr, new_addr, orig_addr, backup_len, dlinfo);
+  else
+    r = sh_switch_hook_shared(target_addr, new_addr, orig_addr, backup_len, dlinfo);
+
+  if (0 == r)
+    SH_LOG_INFO("switch: hook(invisible) in %s mode OK: target_addr %" PRIxPTR ", new_addr %" PRIxPTR,
+                SHADOWHOOK_IS_UNIQUE_MODE ? "UNIQUE" : "SHARED", target_addr, new_addr);
+  return r;
+}
+
+static int sh_switch_unhook_unique(uintptr_t target_addr) {
+  int r;
+  sh_switch_t *useless = NULL;
+
+  pthread_rwlock_wrlock(&sh_switches_lock);  // SYNC - start
+
+  sh_switch_t key = {.target_addr = target_addr};
+  sh_switch_t *self = RB_FIND(sh_switch_tree, &sh_switches, &key);
+  if (NULL == self) {
+    r = SHADOWHOOK_ERRNO_UNHOOK_NOTFOUND;
+    goto end;
+  }
+  r = sh_inst_unhook(&self->inst, target_addr);
+  RB_REMOVE(sh_switch_tree, &sh_switches, self);
+  useless = self;
+
+end:
+  pthread_rwlock_unlock(&sh_switches_lock);  // SYNC - end
+  if (NULL != useless) sh_switch_destroy(useless, false);
+  return r;
+}
+
+static int sh_switch_unhook_shared(uintptr_t target_addr, uintptr_t new_addr) {
+  int r;
+  sh_switch_t *useless = NULL;
+
+  pthread_rwlock_wrlock(&sh_switches_lock);  // SYNC - start
+
+  sh_switch_t key = {.target_addr = target_addr};
+  sh_switch_t *self = RB_FIND(sh_switch_tree, &sh_switches, &key);
+  if (NULL == self) {
+    r = SHADOWHOOK_ERRNO_UNHOOK_NOTFOUND;
+    goto end;
+  }
+
+  // delete proxy in hub
+  bool have_enabled_proxy;
+  if (0 != sh_hub_del_proxy(self->hub, new_addr, &have_enabled_proxy)) {
+    r = SHADOWHOOK_ERRNO_UNHOOK_NOTFOUND;
+    goto end;
+  }
+  r = 0;
+
+  // unhook inst, remove current switch from switch-tree
+  if (!have_enabled_proxy) {
+    r = sh_inst_unhook(&self->inst, target_addr);
+
+    uintptr_t *safe_orig_addr_addr = sh_safe_get_orig_addr_addr(target_addr);
+    if (NULL != safe_orig_addr_addr) __atomic_store_n(safe_orig_addr_addr, 0, __ATOMIC_SEQ_CST);
+
+    RB_REMOVE(sh_switch_tree, &sh_switches, self);
+    useless = self;
+  }
+
+end:
+  pthread_rwlock_unlock(&sh_switches_lock);  // SYNC - end
+  if (NULL != useless) sh_switch_destroy(useless, true);
+  return r;
+}
+
+int sh_switch_unhook(uintptr_t target_addr, uintptr_t new_addr) {
+  int r;
+  if (SHADOWHOOK_IS_UNIQUE_MODE) {
+    r = sh_switch_unhook_unique(target_addr);
+    if (0 == r) SH_LOG_INFO("switch: unhook in UNIQUE mode OK: target_addr %" PRIxPTR, target_addr);
+  } else {
+    r = sh_switch_unhook_shared(target_addr, new_addr);
+    if (0 == r)
+      SH_LOG_INFO("switch: unhook in SHARED mode OK: target_addr %" PRIxPTR ", new_addr %" PRIxPTR,
+                  target_addr, new_addr);
+  }
+
+  return r;
+}
diff --git a/app/src/main/cpp/shadowhook/sh_switch.h b/app/src/main/cpp/shadowhook/sh_switch.h
new file mode 100644
index 0000000000000000000000000000000000000000..3899a9344686b25eb4035410e59b54ec93ab66aa
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/sh_switch.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#pragma once
+#include <stdint.h>
+
+#include "xdl.h"
+
+int sh_switch_hook(uintptr_t target_addr, uintptr_t new_addr, uintptr_t *orig_addr, size_t *backup_len,
+                   xdl_info_t *dlinfo);
+int sh_switch_unhook(uintptr_t target_addr, uintptr_t new_addr);
+
+int sh_switch_hook_invisible(uintptr_t target_addr, uintptr_t new_addr, uintptr_t *orig_addr,
+                             size_t *backup_len, xdl_info_t *dlinfo);
diff --git a/app/src/main/cpp/shadowhook/sh_task.c b/app/src/main/cpp/shadowhook/sh_task.c
new file mode 100644
index 0000000000000000000000000000000000000000..49ad751d96eb40c6228b10f0c14a26a8b4eedd87
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/sh_task.c
@@ -0,0 +1,333 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#include "sh_task.h"
+
+#include <inttypes.h>
+#include <malloc.h>
+#include <poll.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/eventfd.h>
+#include <unistd.h>
+
+#include "queue.h"
+#include "sh_config.h"
+#include "sh_errno.h"
+#include "sh_linker.h"
+#include "sh_log.h"
+#include "sh_recorder.h"
+#include "sh_sig.h"
+#include "sh_switch.h"
+#include "sh_util.h"
+#include "shadowhook.h"
+#include "xdl.h"
+
+#define SH_TASK_THREAD_NAME "shadowhook-task"
+
+// task
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+struct sh_task {
+  char *lib_name;
+  char *sym_name;
+  uintptr_t target_addr;
+  uintptr_t new_addr;
+  uintptr_t *orig_addr;
+  shadowhook_hooked_t hooked;
+  void *hooked_arg;
+  uintptr_t caller_addr;
+  bool finished;
+  bool error;
+  bool ignore_symbol_check;
+  TAILQ_ENTRY(sh_task, ) link;
+};
+#pragma clang diagnostic pop
+
+// task queue
+typedef TAILQ_HEAD(sh_task_queue, sh_task, ) sh_task_queue_t;
+
+// task queue object
+static sh_task_queue_t sh_tasks = TAILQ_HEAD_INITIALIZER(sh_tasks);
+static pthread_rwlock_t sh_tasks_lock = PTHREAD_RWLOCK_INITIALIZER;
+static int sh_tasks_unfinished_cnt = 0;
+
+static int sh_task_eventfd;
+static int thread_started_result = SHADOWHOOK_ERRNO_MONITOR_THREAD;
+
+sh_task_t *sh_task_create_by_target_addr(uintptr_t target_addr, uintptr_t new_addr, uintptr_t *orig_addr,
+                                         bool ignore_symbol_check, uintptr_t caller_addr) {
+  sh_task_t *self = malloc(sizeof(sh_task_t));
+  if (NULL == self) return NULL;
+  self->lib_name = NULL;
+  self->sym_name = NULL;
+  self->target_addr = target_addr;
+  self->new_addr = new_addr;
+  self->orig_addr = orig_addr;
+  self->hooked = NULL;
+  self->hooked_arg = NULL;
+  self->caller_addr = caller_addr;
+  self->finished = false;
+  self->error = false;
+  self->ignore_symbol_check = ignore_symbol_check;
+
+  return self;
+}
+
+sh_task_t *sh_task_create_by_sym_name(const char *lib_name, const char *sym_name, uintptr_t new_addr,
+                                      uintptr_t *orig_addr, shadowhook_hooked_t hooked, void *hooked_arg,
+                                      uintptr_t caller_addr) {
+  sh_task_t *self = malloc(sizeof(sh_task_t));
+  if (NULL == self) return NULL;
+
+  if (NULL == (self->lib_name = strdup(lib_name))) goto err;
+  if (NULL == (self->sym_name = strdup(sym_name))) goto err;
+  self->target_addr = 0;
+  self->new_addr = new_addr;
+  self->orig_addr = orig_addr;
+  self->hooked = hooked;
+  self->hooked_arg = hooked_arg;
+  self->caller_addr = caller_addr;
+  self->finished = false;
+  self->error = false;
+  self->ignore_symbol_check = false;
+
+  return self;
+
+err:
+  if (NULL != self->lib_name) free(self->lib_name);
+  if (NULL != self->sym_name) free(self->sym_name);
+  free(self);
+  return NULL;
+}
+
+void sh_task_destroy(sh_task_t *self) {
+  if (NULL != self->lib_name) free(self->lib_name);
+  if (NULL != self->sym_name) free(self->sym_name);
+  free(self);
+}
+
+static void sh_task_do_callback(sh_task_t *self, int error_number) {
+  if (NULL != self->hooked)
+    self->hooked(error_number, self->lib_name, self->sym_name, (void *)self->target_addr,
+                 (void *)self->new_addr, self->orig_addr, self->hooked_arg);
+}
+
+static int sh_task_hook_pending(struct dl_phdr_info *info, size_t size, void *arg) {
+  (void)size, (void)arg;
+
+  pthread_rwlock_rdlock(&sh_tasks_lock);
+
+  sh_task_t *task;
+  TAILQ_FOREACH(task, &sh_tasks, link) {
+    if (task->finished) continue;
+    if ('/' == info->dlpi_name[0] && NULL == strstr(info->dlpi_name, task->lib_name)) continue;
+    if ('/' != info->dlpi_name[0] && NULL == strstr(task->lib_name, info->dlpi_name)) continue;
+
+    xdl_info_t dlinfo;
+    char real_lib_name[512];
+    int r = sh_linker_get_dlinfo_by_sym_name(task->lib_name, task->sym_name, &dlinfo, real_lib_name,
+                                             sizeof(real_lib_name));
+    task->target_addr = (uintptr_t)dlinfo.dli_saddr;
+    if (SHADOWHOOK_ERRNO_PENDING != r) {
+      size_t backup_len = 0;
+      if (0 == r) {
+        r = sh_switch_hook(task->target_addr, task->new_addr, task->orig_addr, &backup_len, &dlinfo);
+        if (0 != r) task->error = true;
+      } else {
+        strlcpy(real_lib_name, task->lib_name, sizeof(real_lib_name));
+        task->error = true;
+      }
+      sh_recorder_add_hook(r, false, task->target_addr, real_lib_name, task->sym_name, task->new_addr,
+                           backup_len, (uintptr_t)task, task->caller_addr);
+      task->finished = true;
+      sh_task_do_callback(task, r);
+      if (0 == __atomic_sub_fetch(&sh_tasks_unfinished_cnt, 1, __ATOMIC_SEQ_CST)) break;
+    }
+  }
+
+  pthread_rwlock_unlock(&sh_tasks_lock);
+
+  return __atomic_load_n(&sh_tasks_unfinished_cnt, __ATOMIC_SEQ_CST) > 0 ? 0 : 1;
+}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wgnu-statement-expression"
+
+static void sh_task_post_dlopen_callback(void *arg) {
+  (void)arg;
+
+  if (0 == thread_started_result && __atomic_load_n(&sh_tasks_unfinished_cnt, __ATOMIC_SEQ_CST) > 0) {
+    uint64_t ev_val = 1;
+    SH_UTIL_TEMP_FAILURE_RETRY(write(sh_task_eventfd, &ev_val, sizeof(ev_val)));
+  }
+}
+
+__noreturn static void *sh_task_thread_func(void *arg) {
+  (void)arg;
+  pthread_t thread = pthread_self();
+  pthread_setname_np(thread, SH_TASK_THREAD_NAME);
+  pthread_detach(thread);
+
+  struct pollfd ev = {.fd = sh_task_eventfd, .events = POLLIN, .revents = 0};
+  while (1) {
+    int n = SH_UTIL_TEMP_FAILURE_RETRY(poll(&ev, 1, -1));
+    if (n < 0) {
+      sleep(1);
+      continue;
+    } else if (n > 0) {
+      uint64_t ev_val;
+      SH_UTIL_TEMP_FAILURE_RETRY(read(sh_task_eventfd, &ev_val, sizeof(ev_val)));
+
+      if (sh_util_get_api_level() >= __ANDROID_API_L__) {
+        xdl_iterate_phdr(sh_task_hook_pending, NULL, XDL_DEFAULT);
+      } else {
+        SH_SIG_TRY(SIGSEGV, SIGBUS) {
+          xdl_iterate_phdr(sh_task_hook_pending, NULL, XDL_DEFAULT);
+        }
+        SH_SIG_CATCH() {
+          SH_LOG_WARN("task: dliterate crashed");
+        }
+        SH_SIG_EXIT
+      }
+    }
+  }
+}
+
+#pragma clang diagnostic pop
+
+static int sh_task_start_monitor(bool start_thread) {
+  static bool thread_started = false;
+  static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+  pthread_t thread;
+  int r;
+
+  // hook linker dlopen()
+  if (0 != (r = sh_linker_hook_dlopen(sh_task_post_dlopen_callback, NULL))) return r;
+
+  if (!start_thread) return 0;
+
+  // start thread
+  if (thread_started) return thread_started_result;
+  pthread_mutex_lock(&lock);
+  if (thread_started) goto end;
+
+  if (0 > (sh_task_eventfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC))) goto end;
+  if (0 != pthread_create(&thread, NULL, &sh_task_thread_func, NULL)) goto end;
+
+  // OK
+  thread_started_result = 0;
+
+end:
+  thread_started = true;
+  pthread_mutex_unlock(&lock);
+  SH_LOG_INFO("task: start monitor %s, return: %d", 0 == thread_started_result ? "OK" : "FAILED",
+              thread_started_result);
+  return thread_started_result;
+}
+
+int sh_task_hook(sh_task_t *self) {
+  int r;
+  bool is_hook_sym_addr = true;
+  char real_lib_name[512] = "unknown";
+  char real_sym_name[1024] = "unknown";
+  size_t backup_len = 0;
+
+  // find target-address by library-name and symbol-name
+  xdl_info_t dlinfo;
+  memset(&dlinfo, 0, sizeof(xdl_info_t));
+  if (0 == self->target_addr) {
+    is_hook_sym_addr = false;
+    strlcpy(real_lib_name, self->lib_name, sizeof(real_lib_name));
+    strlcpy(real_sym_name, self->sym_name, sizeof(real_sym_name));
+    r = sh_linker_get_dlinfo_by_sym_name(self->lib_name, self->sym_name, &dlinfo, real_lib_name,
+                                         sizeof(real_lib_name));
+    if (SHADOWHOOK_ERRNO_PENDING == r) {
+      // we need to start monitor linker dlopen for handle the pending task
+      if (0 != (r = sh_task_start_monitor(true))) goto end;
+      r = SHADOWHOOK_ERRNO_PENDING;
+      goto end;
+    }
+    if (0 != r) goto end;                             // error
+    self->target_addr = (uintptr_t)dlinfo.dli_saddr;  // OK
+  } else {
+    r = sh_linker_get_dlinfo_by_addr((void *)self->target_addr, &dlinfo, real_lib_name, sizeof(real_lib_name),
+                                     real_sym_name, sizeof(real_sym_name), self->ignore_symbol_check);
+    if (0 != r) goto end;  // error
+  }
+
+  // In UNIQUE mode, if external users are hooking the linker dlopen() or do_dlopen(),
+  // we MUST hook this method with invisible for ourself first.
+  if (sh_linker_need_to_hook_dlopen(self->target_addr)) {
+    SH_LOG_INFO("task: hook dlopen/do_dlopen internal. target-address %" PRIxPTR, self->target_addr);
+    if (0 != (r = sh_task_start_monitor(false))) goto end;
+  }
+
+  // hook by target-address
+  r = sh_switch_hook(self->target_addr, self->new_addr, self->orig_addr, &backup_len, &dlinfo);
+  self->finished = true;
+
+end:
+  if (0 == r || SHADOWHOOK_ERRNO_PENDING == r)  // "PENDING" is NOT an error
+  {
+    pthread_rwlock_wrlock(&sh_tasks_lock);
+    TAILQ_INSERT_TAIL(&sh_tasks, self, link);
+    if (!self->finished) __atomic_add_fetch(&sh_tasks_unfinished_cnt, 1, __ATOMIC_SEQ_CST);
+    pthread_rwlock_unlock(&sh_tasks_lock);
+  }
+
+  // record
+  sh_recorder_add_hook(r, is_hook_sym_addr, self->target_addr, real_lib_name, real_sym_name, self->new_addr,
+                       backup_len, (uintptr_t)self, self->caller_addr);
+
+  return r;
+}
+
+int sh_task_unhook(sh_task_t *self, uintptr_t caller_addr) {
+  pthread_rwlock_wrlock(&sh_tasks_lock);
+  TAILQ_REMOVE(&sh_tasks, self, link);
+  if (!self->finished) __atomic_sub_fetch(&sh_tasks_unfinished_cnt, 1, __ATOMIC_SEQ_CST);
+  pthread_rwlock_unlock(&sh_tasks_lock);
+
+  // check task status
+  int r;
+  if (self->error) {
+    r = SHADOWHOOK_ERRNO_UNHOOK_ON_ERROR;
+    goto end;
+  }
+  if (!self->finished) {
+    r = SHADOWHOOK_ERRNO_UNHOOK_ON_UNFINISHED;
+    goto end;
+  }
+
+  // do unhook
+  r = sh_switch_unhook(self->target_addr, self->new_addr);
+
+end:
+  // record
+  sh_recorder_add_unhook(r, (uintptr_t)self, caller_addr);
+  return r;
+}
diff --git a/app/src/main/cpp/shadowhook/sh_task.h b/app/src/main/cpp/shadowhook/sh_task.h
new file mode 100644
index 0000000000000000000000000000000000000000..e26aef9241f4df35314b2ae166f5da501fcac7cd
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/sh_task.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#pragma once
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "shadowhook.h"
+
+typedef struct sh_task sh_task_t;
+
+sh_task_t *sh_task_create_by_target_addr(uintptr_t target_addr, uintptr_t new_addr, uintptr_t *orig_addr,
+                                         bool ignore_symbol_check, uintptr_t caller_addr);
+sh_task_t *sh_task_create_by_sym_name(const char *lib_name, const char *sym_name, uintptr_t new_addr,
+                                      uintptr_t *orig_addr, shadowhook_hooked_t hooked, void *hooked_arg,
+                                      uintptr_t caller_addr);
+void sh_task_destroy(sh_task_t *self);
+
+int sh_task_hook(sh_task_t *self);
+int sh_task_unhook(sh_task_t *self, uintptr_t caller_addr);
diff --git a/app/src/main/cpp/shadowhook/shadowhook.c b/app/src/main/cpp/shadowhook/shadowhook.c
new file mode 100644
index 0000000000000000000000000000000000000000..254cda1b3c08d6dd80bb73664f26159435ed4fb0
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/shadowhook.c
@@ -0,0 +1,328 @@
+// Copyright (c) 2021-2022 ByteDance Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
+
+#include "shadowhook.h"
+
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include "bytesig.h"
+#include "sh_enter.h"
+#include "sh_errno.h"
+#include "sh_exit.h"
+#include "sh_hub.h"
+#include "sh_linker.h"
+#include "sh_log.h"
+#include "sh_recorder.h"
+#include "sh_safe.h"
+#include "sh_sig.h"
+#include "sh_switch.h"
+#include "sh_task.h"
+#include "sh_util.h"
+#include "xdl.h"
+
+#define GOTO_ERR(errnum) \
+  do {                   \
+    r = errnum;          \
+    goto err;            \
+  } while (0)
+
+static int shadowhook_init_errno = SHADOWHOOK_ERRNO_UNINIT;
+static shadowhook_mode_t shadowhook_mode = SHADOWHOOK_MODE_SHARED;
+
+const char *shadowhook_get_version(void) {
+  return "shadowhook version " SHADOWHOOK_VERSION;
+}
+
+int shadowhook_init(shadowhook_mode_t mode, bool debuggable) {
+  bool do_init = false;
+
+  if (__predict_true(SHADOWHOOK_ERRNO_UNINIT == shadowhook_init_errno)) {
+    static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+    pthread_mutex_lock(&lock);
+    if (__predict_true(SHADOWHOOK_ERRNO_UNINIT == shadowhook_init_errno)) {
+      do_init = true;
+      shadowhook_mode = mode;
+      sh_log_set_debuggable(debuggable);
+
+#define GOTO_END(errnum)            \
+  do {                              \
+    shadowhook_init_errno = errnum; \
+    goto end;                       \
+  } while (0)
+
+      if (__predict_false(0 != sh_errno_init())) GOTO_END(SHADOWHOOK_ERRNO_INIT_ERRNO);
+      if (__predict_false(0 != bytesig_init(SIGSEGV))) GOTO_END(SHADOWHOOK_ERRNO_INIT_SIGSEGV);
+      if (__predict_false(0 != bytesig_init(SIGBUS))) GOTO_END(SHADOWHOOK_ERRNO_INIT_SIGBUS);
+      if (__predict_false(0 != sh_enter_init())) GOTO_END(SHADOWHOOK_ERRNO_INIT_ENTER);
+      sh_exit_init();
+      if (SHADOWHOOK_MODE_SHARED == shadowhook_mode) {
+        if (__predict_false(0 != sh_safe_init())) GOTO_END(SHADOWHOOK_ERRNO_INIT_SAFE);
+        if (__predict_false(0 != sh_hub_init())) GOTO_END(SHADOWHOOK_ERRNO_INIT_HUB);
+      } else {
+        if (__predict_false(0 != sh_linker_init())) GOTO_END(SHADOWHOOK_ERRNO_INIT_LINKER);
+      }
+
+#undef GOTO_END
+
+      shadowhook_init_errno = SHADOWHOOK_ERRNO_OK;
+    }
+  end:
+    pthread_mutex_unlock(&lock);
+  }
+
+  SH_LOG_ALWAYS_SHOW("%s: shadowhook init(mode: %s, debuggable: %s), return: %d, real-init: %s",
+                     shadowhook_get_version(), SHADOWHOOK_MODE_SHARED == mode ? "SHARED" : "UNIQUE",
+                     debuggable ? "true" : "false", shadowhook_init_errno, do_init ? "yes" : "no");
+  SH_ERRNO_SET_RET_ERRNUM(shadowhook_init_errno);
+}
+
+int shadowhook_get_init_errno(void) {
+  return shadowhook_init_errno;
+}
+
+shadowhook_mode_t shadowhook_get_mode(void) {
+  return shadowhook_mode;
+}
+
+bool shadowhook_get_debuggable(void) {
+  return sh_log_get_debuggable();
+}
+
+void shadowhook_set_debuggable(bool debuggable) {
+  sh_log_set_debuggable(debuggable);
+}
+
+bool shadowhook_get_recordable(void) {
+  return sh_recorder_get_recordable();
+}
+
+void shadowhook_set_recordable(bool recordable) {
+  sh_recorder_set_recordable(recordable);
+}
+
+int shadowhook_get_errno(void) {
+  return sh_errno_get();
+}
+
+const char *shadowhook_to_errmsg(int error_number) {
+  return sh_errno_to_errmsg(error_number);
+}
+
+static void *shadowhook_hook_addr_impl(void *sym_addr, void *new_addr, void **orig_addr,
+                                       bool ignore_symbol_check, uintptr_t caller_addr) {
+  SH_LOG_INFO("shadowhook: hook_%s_addr(%p, %p) ...", ignore_symbol_check ? "func" : "sym", sym_addr,
+              new_addr);
+  sh_errno_reset();
+
+  int r;
+  if (NULL == sym_addr || NULL == new_addr) GOTO_ERR(SHADOWHOOK_ERRNO_INVALID_ARG);
+  if (SHADOWHOOK_ERRNO_OK != shadowhook_init_errno) GOTO_ERR(shadowhook_init_errno);
+
+  // create task
+  sh_task_t *task =
+      sh_task_create_by_target_addr((uintptr_t)sym_addr, (uintptr_t)new_addr, (uintptr_t *)orig_addr,
+                                    ignore_symbol_check, (uintptr_t)caller_addr);
+  if (NULL == task) GOTO_ERR(SHADOWHOOK_ERRNO_OOM);
+
+  // do hook
+  r = sh_task_hook(task);
+  if (0 != r) {
+    sh_task_destroy(task);
+    GOTO_ERR(r);
+  }
+
+  // OK
+  SH_LOG_INFO("shadowhook: hook_%s_addr(%p, %p) OK. return: %p", ignore_symbol_check ? "func" : "sym",
+              sym_addr, new_addr, (void *)task);
+  SH_ERRNO_SET_RET(SHADOWHOOK_ERRNO_OK, (void *)task);
+
+err:
+  SH_LOG_ERROR("shadowhook: hook_%s_addr(%p, %p) FAILED. %d - %s", ignore_symbol_check ? "func" : "sym",
+               sym_addr, new_addr, r, sh_errno_to_errmsg(r));
+  SH_ERRNO_SET_RET_NULL(r);
+}
+
+void *shadowhook_hook_func_addr(void *func_addr, void *new_addr, void **orig_addr) {
+  const void *caller_addr = __builtin_return_address(0);
+  return shadowhook_hook_addr_impl(func_addr, new_addr, orig_addr, true, (uintptr_t)caller_addr);
+}
+
+void *shadowhook_hook_sym_addr(void *sym_addr, void *new_addr, void **orig_addr) {
+  const void *caller_addr = __builtin_return_address(0);
+  return shadowhook_hook_addr_impl(sym_addr, new_addr, orig_addr, false, (uintptr_t)caller_addr);
+}
+
+static void *shadowhook_hook_sym_name_impl(const char *lib_name, const char *sym_name, void *new_addr,
+                                           void **orig_addr, shadowhook_hooked_t hooked, void *hooked_arg,
+                                           uintptr_t caller_addr) {
+  SH_LOG_INFO("shadowhook: hook_sym_name(%s, %s, %p) ...", lib_name, sym_name, new_addr);
+  sh_errno_reset();
+
+  int r;
+  if (NULL == lib_name || NULL == sym_name || NULL == new_addr) GOTO_ERR(SHADOWHOOK_ERRNO_INVALID_ARG);
+  if (SHADOWHOOK_ERRNO_OK != shadowhook_init_errno) GOTO_ERR(shadowhook_init_errno);
+
+  // create task
+  sh_task_t *task =
+      sh_task_create_by_sym_name(lib_name, sym_name, (uintptr_t)new_addr, (uintptr_t *)orig_addr, hooked,
+                                 hooked_arg, (uintptr_t)caller_addr);
+  if (NULL == task) GOTO_ERR(SHADOWHOOK_ERRNO_OOM);
+
+  // do hook
+  r = sh_task_hook(task);
+  if (0 != r && SHADOWHOOK_ERRNO_PENDING != r) {
+    sh_task_destroy(task);
+    GOTO_ERR(r);
+  }
+
+  // OK
+  SH_LOG_INFO("shadowhook: hook_sym_name(%s, %s, %p) OK. return: %p. %d - %s", lib_name, sym_name, new_addr,
+              (void *)task, r, sh_errno_to_errmsg(r));
+  SH_ERRNO_SET_RET(r, (void *)task);
+
+err:
+  SH_LOG_ERROR("shadowhook: hook_sym_name(%s, %s, %p) FAILED. %d - %s", lib_name, sym_name, new_addr, r,
+               sh_errno_to_errmsg(r));
+  SH_ERRNO_SET_RET_NULL(r);
+}
+
+void *shadowhook_hook_sym_name(const char *lib_name, const char *sym_name, void *new_addr, void **orig_addr) {
+  const void *caller_addr = __builtin_return_address(0);
+  return shadowhook_hook_sym_name_impl(lib_name, sym_name, new_addr, orig_addr, NULL, NULL,
+                                       (uintptr_t)caller_addr);
+}
+
+void *shadowhook_hook_sym_name_callback(const char *lib_name, const char *sym_name, void *new_addr,
+                                        void **orig_addr, shadowhook_hooked_t hooked, void *hooked_arg) {
+  const void *caller_addr = __builtin_return_address(0);
+  return shadowhook_hook_sym_name_impl(lib_name, sym_name, new_addr, orig_addr, hooked, hooked_arg,
+                                       (uintptr_t)caller_addr);
+}
+
+int shadowhook_unhook(void *stub) {
+  const void *caller_addr = __builtin_return_address(0);
+  SH_LOG_INFO("shadowhook: unhook(%p) ...", stub);
+  sh_errno_reset();
+
+  int r;
+  if (NULL == stub) GOTO_ERR(SHADOWHOOK_ERRNO_INVALID_ARG);
+  if (SHADOWHOOK_ERRNO_OK != shadowhook_init_errno) GOTO_ERR(shadowhook_init_errno);
+
+  sh_task_t *task = (sh_task_t *)stub;
+  r = sh_task_unhook(task, (uintptr_t)caller_addr);
+  sh_task_destroy(task);
+  if (0 != r) GOTO_ERR(r);
+
+  // OK
+  SH_LOG_INFO("shadowhook: unhook(%p) OK", stub);
+  SH_ERRNO_SET_RET_ERRNUM(SHADOWHOOK_ERRNO_OK);
+
+err:
+  SH_LOG_ERROR("shadowhook: unhook(%p) FAILED. %d - %s", stub, r, sh_errno_to_errmsg(r));
+  SH_ERRNO_SET_RET_FAIL(r);
+}
+
+char *shadowhook_get_records(uint32_t item_flags) {
+  return sh_recorder_get(item_flags);
+}
+
+void shadowhook_dump_records(int fd, uint32_t item_flags) {
+  sh_recorder_dump(fd, item_flags);
+}
+
+void *shadowhook_dlopen(const char *lib_name) {
+  void *handle = NULL;
+  if (sh_util_get_api_level() >= __ANDROID_API_L__) {
+    handle = xdl_open(lib_name, XDL_DEFAULT);
+  } else {
+    SH_SIG_TRY(SIGSEGV, SIGBUS) {
+      handle = xdl_open(lib_name, XDL_DEFAULT);
+    }
+    SH_SIG_CATCH() {
+      SH_LOG_WARN("shadowhook: dlopen crashed - %s", lib_name);
+    }
+    SH_SIG_EXIT
+  }
+  return handle;
+}
+
+void shadowhook_dlclose(void *handle) {
+  xdl_close(handle);
+}
+
+void *shadowhook_dlsym(void *handle, const char *sym_name) {
+  void *addr = shadowhook_dlsym_dynsym(handle, sym_name);
+  if (NULL == addr) addr = shadowhook_dlsym_symtab(handle, sym_name);
+  return addr;
+}
+
+void *shadowhook_dlsym_dynsym(void *handle, const char *sym_name) {
+  void *addr = NULL;
+  SH_SIG_TRY(SIGSEGV, SIGBUS) {
+    addr = xdl_sym(handle, sym_name, NULL);
+  }
+  SH_SIG_CATCH() {
+    SH_LOG_WARN("shadowhook: dlsym_dynsym crashed - %p, %s", handle, sym_name);
+  }
+  SH_SIG_EXIT
+  return addr;
+}
+
+void *shadowhook_dlsym_symtab(void *handle, const char *sym_name) {
+  void *addr = NULL;
+  SH_SIG_TRY(SIGSEGV, SIGBUS) {
+    addr = xdl_dsym(handle, sym_name, NULL);
+  }
+  SH_SIG_CATCH() {
+    SH_LOG_WARN("shadowhook: dlsym_symtab crashed - %p, %s", handle, sym_name);
+  }
+  SH_SIG_EXIT
+  return addr;
+}
+
+void *shadowhook_get_prev_func(void *func) {
+  if (__predict_false(SHADOWHOOK_IS_UNIQUE_MODE)) abort();
+  return sh_hub_get_prev_func(func);
+}
+
+void shadowhook_pop_stack(void *return_address) {
+  if (__predict_false(SHADOWHOOK_IS_UNIQUE_MODE)) abort();
+  sh_hub_pop_stack(return_address);
+}
+
+void shadowhook_allow_reentrant(void *return_address) {
+  if (__predict_false(SHADOWHOOK_IS_UNIQUE_MODE)) abort();
+  sh_hub_allow_reentrant(return_address);
+}
+
+void shadowhook_disallow_reentrant(void *return_address) {
+  if (__predict_false(SHADOWHOOK_IS_UNIQUE_MODE)) abort();
+  sh_hub_disallow_reentrant(return_address);
+}
+
+void *shadowhook_get_return_address(void) {
+  if (__predict_false(SHADOWHOOK_IS_UNIQUE_MODE)) abort();
+  return sh_hub_get_return_address();
+}
diff --git a/app/src/main/cpp/shadowhook/third_party/bsd/queue.h b/app/src/main/cpp/shadowhook/third_party/bsd/queue.h
new file mode 100644
index 0000000000000000000000000000000000000000..f550f6c26561849097ef3597e6c0171ad7b6c365
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/third_party/bsd/queue.h
@@ -0,0 +1,551 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *      The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef QUEUE_H
+#define QUEUE_H
+
+/* #include <sys/cdefs.h> */
+#define QUEUE_CONTAINER_OF(ptr, type, field) ((type *)((char *)(ptr) - ((char *)&((type *)0)->field)))
+
+/*
+ * This file defines four types of data structures: singly-linked lists,
+ * singly-linked tail queues, lists and tail queues.
+ *
+ * A singly-linked list is headed by a single forward pointer. The elements
+ * are singly linked for minimum space and pointer manipulation overhead at
+ * the expense of O(n) removal for arbitrary elements. New elements can be
+ * added to the list after an existing element or at the head of the list.
+ * Elements being removed from the head of the list should use the explicit
+ * macro for this purpose for optimum efficiency. A singly-linked list may
+ * only be traversed in the forward direction.  Singly-linked lists are ideal
+ * for applications with large datasets and few or no removals or for
+ * implementing a LIFO queue.
+ *
+ * A singly-linked tail queue is headed by a pair of pointers, one to the
+ * head of the list and the other to the tail of the list. The elements are
+ * singly linked for minimum space and pointer manipulation overhead at the
+ * expense of O(n) removal for arbitrary elements. New elements can be added
+ * to the list after an existing element, at the head of the list, or at the
+ * end of the list. Elements being removed from the head of the tail queue
+ * should use the explicit macro for this purpose for optimum efficiency.
+ * A singly-linked tail queue may only be traversed in the forward direction.
+ * Singly-linked tail queues are ideal for applications with large datasets
+ * and few or no removals or for implementing a FIFO queue.
+ *
+ * A list is headed by a single forward pointer (or an array of forward
+ * pointers for a hash table header). The elements are doubly linked
+ * so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before
+ * or after an existing element or at the head of the list. A list
+ * may be traversed in either direction.
+ *
+ * A tail queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or
+ * after an existing element, at the head of the list, or at the end of
+ * the list. A tail queue may be traversed in either direction.
+ *
+ * For details on the use of these macros, see the queue(3) manual page.
+ *
+ *                              SLIST   LIST    STAILQ  TAILQ
+ * _HEAD                        +       +       +       +
+ * _HEAD_INITIALIZER            +       +       +       +
+ * _ENTRY                       +       +       +       +
+ * _INIT                        +       +       +       +
+ * _EMPTY                       +       +       +       +
+ * _FIRST                       +       +       +       +
+ * _NEXT                        +       +       +       +
+ * _PREV                        -       +       -       +
+ * _LAST                        -       -       +       +
+ * _FOREACH                     +       +       +       +
+ * _FOREACH_FROM                +       +       +       +
+ * _FOREACH_SAFE                +       +       +       +
+ * _FOREACH_FROM_SAFE           +       +       +       +
+ * _FOREACH_REVERSE             -       -       -       +
+ * _FOREACH_REVERSE_FROM        -       -       -       +
+ * _FOREACH_REVERSE_SAFE        -       -       -       +
+ * _FOREACH_REVERSE_FROM_SAFE   -       -       -       +
+ * _INSERT_HEAD                 +       +       +       +
+ * _INSERT_BEFORE               -       +       -       +
+ * _INSERT_AFTER                +       +       +       +
+ * _INSERT_TAIL                 -       -       +       +
+ * _CONCAT                      -       -       +       +
+ * _REMOVE_AFTER                +       -       +       -
+ * _REMOVE_HEAD                 +       -       +       -
+ * _REMOVE                      +       +       +       +
+ * _SWAP                        +       +       +       +
+ *
+ */
+
+/*
+ * Singly-linked List declarations.
+ */
+#define SLIST_HEAD(name, type, qual)                                    \
+    struct name {                                                       \
+        struct type *qual slh_first; /* first element */                \
+    }
+
+#define SLIST_HEAD_INITIALIZER(head)                                    \
+    { NULL }
+
+#define SLIST_ENTRY(type, qual)                                         \
+    struct {                                                            \
+        struct type *qual sle_next;  /* next element */                 \
+    }
+
+/*
+ * Singly-linked List functions.
+ */
+#define SLIST_INIT(head) do {                                           \
+        SLIST_FIRST((head)) = NULL;                                     \
+    } while (0)
+
+#define SLIST_EMPTY(head) ((head)->slh_first == NULL)
+
+#define SLIST_FIRST(head) ((head)->slh_first)
+
+#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
+
+#define SLIST_FOREACH(var, head, field)                                 \
+    for ((var) = SLIST_FIRST((head));                                   \
+         (var);                                                         \
+         (var) = SLIST_NEXT((var), field))
+
+#define SLIST_FOREACH_FROM(var, head, field)                            \
+    for ((var) = ((var) ? (var) : SLIST_FIRST((head)));                 \
+         (var);                                                         \
+         (var) = SLIST_NEXT((var), field))
+
+#define SLIST_FOREACH_SAFE(var, head, field, tvar)                      \
+    for ((var) = SLIST_FIRST((head));                                   \
+         (var) && ((tvar) = SLIST_NEXT((var), field), 1);               \
+         (var) = (tvar))
+
+#define SLIST_FOREACH_FROM_SAFE(var, head, field, tvar)                 \
+    for ((var) = ((var) ? (var) : SLIST_FIRST((head)));                 \
+         (var) && ((tvar) = SLIST_NEXT((var), field), 1);               \
+         (var) = (tvar))
+
+#define SLIST_INSERT_HEAD(head, elm, field) do {                        \
+        SLIST_NEXT((elm), field) = SLIST_FIRST((head));                 \
+        SLIST_FIRST((head)) = (elm);                                    \
+    } while (0)
+
+#define SLIST_INSERT_AFTER(slistelm, elm, field) do {                   \
+        SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field);       \
+        SLIST_NEXT((slistelm), field) = (elm);                          \
+    } while (0)
+
+#define SLIST_REMOVE_AFTER(elm, field) do {                             \
+        SLIST_NEXT(elm, field) =                                        \
+            SLIST_NEXT(SLIST_NEXT(elm, field), field);                  \
+    } while (0)
+
+#define SLIST_REMOVE_HEAD(head, field) do {                             \
+        SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field);   \
+    } while (0)
+
+#define SLIST_REMOVE(head, elm, type, field) do {                       \
+        if (SLIST_FIRST((head)) == (elm)) {                             \
+            SLIST_REMOVE_HEAD((head), field);                           \
+        }                                                               \
+        else {                                                          \
+            struct type *curelm = SLIST_FIRST((head));                  \
+            while (SLIST_NEXT(curelm, field) != (elm))                  \
+                curelm = SLIST_NEXT(curelm, field);                     \
+            SLIST_REMOVE_AFTER(curelm, field);                          \
+        }                                                               \
+    } while (0)
+
+#define SLIST_SWAP(head1, head2, type) do {                             \
+        struct type *swap_first = SLIST_FIRST(head1);                   \
+        SLIST_FIRST(head1) = SLIST_FIRST(head2);                        \
+        SLIST_FIRST(head2) = swap_first;                                \
+    } while (0)
+
+/*
+ * List declarations.
+ */
+#define LIST_HEAD(name, type, qual)                                     \
+    struct name {                                                       \
+        struct type *qual lh_first;  /* first element */                \
+    }
+
+#define LIST_HEAD_INITIALIZER(head)                                     \
+    { NULL }
+
+#define LIST_ENTRY(type, qual)                                          \
+    struct {                                                            \
+        struct type *qual le_next;   /* next element */                 \
+        struct type *qual *le_prev;  /* address of previous next element */ \
+    }
+
+/*
+ * List functions.
+ */
+#define LIST_INIT(head) do {                                            \
+        LIST_FIRST((head)) = NULL;                                      \
+    } while (0)
+
+#define LIST_EMPTY(head) ((head)->lh_first == NULL)
+
+#define LIST_FIRST(head) ((head)->lh_first)
+
+#define LIST_NEXT(elm, field) ((elm)->field.le_next)
+
+#define LIST_PREV(elm, head, type, field)                               \
+    ((elm)->field.le_prev == &LIST_FIRST((head)) ? NULL :               \
+     QUEUE_CONTAINER_OF((elm)->field.le_prev, struct type, field.le_next))
+
+#define LIST_FOREACH(var, head, field)                                  \
+    for ((var) = LIST_FIRST((head));                                    \
+         (var);                                                         \
+         (var) = LIST_NEXT((var), field))
+
+#define LIST_FOREACH_FROM(var, head, field)                             \
+    for ((var) = ((var) ? (var) : LIST_FIRST((head)));                  \
+         (var);                                                         \
+         (var) = LIST_NEXT((var), field))
+
+#define LIST_FOREACH_SAFE(var, head, field, tvar)                       \
+    for ((var) = LIST_FIRST((head));                                    \
+         (var) && ((tvar) = LIST_NEXT((var), field), 1);                \
+         (var) = (tvar))
+
+#define LIST_FOREACH_FROM_SAFE(var, head, field, tvar)                  \
+    for ((var) = ((var) ? (var) : LIST_FIRST((head)));                  \
+         (var) && ((tvar) = LIST_NEXT((var), field), 1);                \
+         (var) = (tvar))
+
+#define LIST_INSERT_HEAD(head, elm, field) do {                         \
+        if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL)     \
+            LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field); \
+        LIST_FIRST((head)) = (elm);                                     \
+        (elm)->field.le_prev = &LIST_FIRST((head));                     \
+    } while (0)
+
+#define LIST_INSERT_BEFORE(listelm, elm, field) do {                    \
+        (elm)->field.le_prev = (listelm)->field.le_prev;                \
+        LIST_NEXT((elm), field) = (listelm);                            \
+        *(listelm)->field.le_prev = (elm);                              \
+        (listelm)->field.le_prev = &LIST_NEXT((elm), field);            \
+    } while (0)
+
+#define LIST_INSERT_AFTER(listelm, elm, field) do {                     \
+        if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL) \
+            LIST_NEXT((listelm), field)->field.le_prev =                \
+                &LIST_NEXT((elm), field);                               \
+        LIST_NEXT((listelm), field) = (elm);                            \
+        (elm)->field.le_prev = &LIST_NEXT((listelm), field);            \
+    } while (0)
+
+#define LIST_REMOVE(elm, field) do {                                    \
+        if (LIST_NEXT((elm), field) != NULL)                            \
+            LIST_NEXT((elm), field)->field.le_prev =                    \
+                (elm)->field.le_prev;                                   \
+        *(elm)->field.le_prev = LIST_NEXT((elm), field);                \
+    } while (0)
+
+#define LIST_SWAP(head1, head2, type, field) do {                       \
+        struct type *swap_tmp = LIST_FIRST((head1));                    \
+        LIST_FIRST((head1)) = LIST_FIRST((head2));                      \
+        LIST_FIRST((head2)) = swap_tmp;                                 \
+        if ((swap_tmp = LIST_FIRST((head1))) != NULL)                   \
+            swap_tmp->field.le_prev = &LIST_FIRST((head1));             \
+        if ((swap_tmp = LIST_FIRST((head2))) != NULL)                   \
+            swap_tmp->field.le_prev = &LIST_FIRST((head2));             \
+    } while (0)
+
+/*
+ * Singly-linked Tail queue declarations.
+ */
+#define STAILQ_HEAD(name, type, qual)                                   \
+    struct name {                                                       \
+        struct type *qual stqh_first;/* first element */                \
+        struct type *qual *stqh_last;/* addr of last next element */    \
+    }
+
+#define STAILQ_HEAD_INITIALIZER(head)                                   \
+    { NULL, &(head).stqh_first }
+
+#define STAILQ_ENTRY(type, qual)                                        \
+    struct {                                                            \
+        struct type *qual stqe_next; /* next element */                 \
+    }
+
+/*
+ * Singly-linked Tail queue functions.
+ */
+#define STAILQ_INIT(head) do {                                          \
+        STAILQ_FIRST((head)) = NULL;                                    \
+        (head)->stqh_last = &STAILQ_FIRST((head));                      \
+    } while (0)
+
+#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL)
+
+#define STAILQ_FIRST(head) ((head)->stqh_first)
+
+#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
+
+#define STAILQ_LAST(head, type, field)                                  \
+    (STAILQ_EMPTY((head)) ? NULL :                                      \
+     QUEUE_CONTAINER_OF((head)->stqh_last, struct type, field.stqe_next))
+
+#define STAILQ_FOREACH(var, head, field)                                \
+    for((var) = STAILQ_FIRST((head));                                   \
+        (var);                                                          \
+        (var) = STAILQ_NEXT((var), field))
+
+#define STAILQ_FOREACH_FROM(var, head, field)                           \
+    for ((var) = ((var) ? (var) : STAILQ_FIRST((head)));                \
+         (var);                                                         \
+         (var) = STAILQ_NEXT((var), field))
+
+#define STAILQ_FOREACH_SAFE(var, head, field, tvar)                     \
+    for ((var) = STAILQ_FIRST((head));                                  \
+         (var) && ((tvar) = STAILQ_NEXT((var), field), 1);              \
+         (var) = (tvar))
+
+#define STAILQ_FOREACH_FROM_SAFE(var, head, field, tvar)                \
+    for ((var) = ((var) ? (var) : STAILQ_FIRST((head)));                \
+         (var) && ((tvar) = STAILQ_NEXT((var), field), 1);              \
+         (var) = (tvar))
+
+#define STAILQ_INSERT_HEAD(head, elm, field) do {                       \
+        if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \
+            (head)->stqh_last = &STAILQ_NEXT((elm), field);             \
+        STAILQ_FIRST((head)) = (elm);                                   \
+    } while (0)
+
+#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do {               \
+        if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL) \
+            (head)->stqh_last = &STAILQ_NEXT((elm), field);             \
+        STAILQ_NEXT((tqelm), field) = (elm);                            \
+    } while (0)
+
+#define STAILQ_INSERT_TAIL(head, elm, field) do {                       \
+        STAILQ_NEXT((elm), field) = NULL;                               \
+        *(head)->stqh_last = (elm);                                     \
+        (head)->stqh_last = &STAILQ_NEXT((elm), field);                 \
+    } while (0)
+
+#define STAILQ_CONCAT(head1, head2) do {                                \
+        if (!STAILQ_EMPTY((head2))) {                                   \
+            *(head1)->stqh_last = (head2)->stqh_first;                  \
+            (head1)->stqh_last = (head2)->stqh_last;                    \
+            STAILQ_INIT((head2));                                       \
+        }                                                               \
+    } while (0)
+
+#define STAILQ_REMOVE_AFTER(head, elm, field) do {                      \
+        if ((STAILQ_NEXT(elm, field) =                                  \
+             STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL)      \
+            (head)->stqh_last = &STAILQ_NEXT((elm), field);             \
+    } while (0)
+
+#define STAILQ_REMOVE_HEAD(head, field) do {                            \
+        if ((STAILQ_FIRST((head)) =                                     \
+             STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL)         \
+            (head)->stqh_last = &STAILQ_FIRST((head));                  \
+    } while (0)
+
+#define STAILQ_REMOVE(head, elm, type, field) do {                      \
+        if (STAILQ_FIRST((head)) == (elm)) {                            \
+            STAILQ_REMOVE_HEAD((head), field);                          \
+        }                                                               \
+        else {                                                          \
+            struct type *curelm = STAILQ_FIRST((head));                 \
+            while (STAILQ_NEXT(curelm, field) != (elm))                 \
+                curelm = STAILQ_NEXT(curelm, field);                    \
+            STAILQ_REMOVE_AFTER(head, curelm, field);                   \
+        }                                                               \
+    } while (0)
+
+#define STAILQ_SWAP(head1, head2, type) do {                            \
+        struct type *swap_first = STAILQ_FIRST(head1);                  \
+        struct type **swap_last = (head1)->stqh_last;                   \
+        STAILQ_FIRST(head1) = STAILQ_FIRST(head2);                      \
+        (head1)->stqh_last = (head2)->stqh_last;                        \
+        STAILQ_FIRST(head2) = swap_first;                               \
+        (head2)->stqh_last = swap_last;                                 \
+        if (STAILQ_EMPTY(head1))                                        \
+            (head1)->stqh_last = &STAILQ_FIRST(head1);                  \
+        if (STAILQ_EMPTY(head2))                                        \
+            (head2)->stqh_last = &STAILQ_FIRST(head2);                  \
+    } while (0)
+
+/*
+ * Tail queue declarations.
+ */
+#define TAILQ_HEAD(name, type, qual)                                    \
+    struct name {                                                       \
+        struct type *qual tqh_first; /* first element */                \
+        struct type *qual *tqh_last; /* addr of last next element */    \
+}
+
+#define TAILQ_HEAD_INITIALIZER(head)                                    \
+    { NULL, &(head).tqh_first }
+
+#define TAILQ_ENTRY(type, qual)                                         \
+    struct {                                                            \
+        struct type *qual tqe_next;  /* next element */                 \
+        struct type *qual *tqe_prev; /* address of previous next element */ \
+    }
+
+/*
+ * Tail queue functions.
+ */
+#define TAILQ_INIT(head) do {                                           \
+        TAILQ_FIRST((head)) = NULL;                                     \
+        (head)->tqh_last = &TAILQ_FIRST((head));                        \
+    } while (0)
+
+#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
+
+#define TAILQ_FIRST(head) ((head)->tqh_first)
+
+#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
+
+#define TAILQ_PREV(elm, headname, field)                                \
+        (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+
+#define TAILQ_LAST(head, headname)                                      \
+    (*(((struct headname *)((head)->tqh_last))->tqh_last))
+
+#define TAILQ_FOREACH(var, head, field)                                 \
+    for ((var) = TAILQ_FIRST((head));                                   \
+         (var);                                                         \
+         (var) = TAILQ_NEXT((var), field))
+
+#define TAILQ_FOREACH_FROM(var, head, field)                            \
+    for ((var) = ((var) ? (var) : TAILQ_FIRST((head)));                 \
+         (var);                                                         \
+         (var) = TAILQ_NEXT((var), field))
+
+#define TAILQ_FOREACH_SAFE(var, head, field, tvar)                      \
+    for ((var) = TAILQ_FIRST((head));                                   \
+         (var) && ((tvar) = TAILQ_NEXT((var), field), 1);               \
+         (var) = (tvar))
+
+#define TAILQ_FOREACH_FROM_SAFE(var, head, field, tvar)                 \
+    for ((var) = ((var) ? (var) : TAILQ_FIRST((head)));                 \
+         (var) && ((tvar) = TAILQ_NEXT((var), field), 1);               \
+         (var) = (tvar))
+
+#define TAILQ_FOREACH_REVERSE(var, head, headname, field)               \
+    for ((var) = TAILQ_LAST((head), headname);                          \
+         (var);                                                         \
+         (var) = TAILQ_PREV((var), headname, field))
+
+#define TAILQ_FOREACH_REVERSE_FROM(var, head, headname, field)          \
+    for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname));        \
+         (var);                                                         \
+         (var) = TAILQ_PREV((var), headname, field))
+
+#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar)    \
+    for ((var) = TAILQ_LAST((head), headname);                          \
+         (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1);     \
+         (var) = (tvar))
+
+#define TAILQ_FOREACH_REVERSE_FROM_SAFE(var, head, headname, field, tvar) \
+    for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname));        \
+         (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1);     \
+         (var) = (tvar))
+
+#define TAILQ_INSERT_HEAD(head, elm, field) do {                        \
+        if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL)   \
+            TAILQ_FIRST((head))->field.tqe_prev =                       \
+                &TAILQ_NEXT((elm), field);                              \
+        else                                                            \
+            (head)->tqh_last = &TAILQ_NEXT((elm), field);               \
+        TAILQ_FIRST((head)) = (elm);                                    \
+        (elm)->field.tqe_prev = &TAILQ_FIRST((head));                   \
+    } while (0)
+
+#define TAILQ_INSERT_BEFORE(listelm, elm, field) do {                   \
+        (elm)->field.tqe_prev = (listelm)->field.tqe_prev;              \
+        TAILQ_NEXT((elm), field) = (listelm);                           \
+        *(listelm)->field.tqe_prev = (elm);                             \
+        (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field);          \
+    } while (0)
+
+#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do {              \
+        if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL) \
+            TAILQ_NEXT((elm), field)->field.tqe_prev =                  \
+                &TAILQ_NEXT((elm), field);                              \
+        else                                                            \
+            (head)->tqh_last = &TAILQ_NEXT((elm), field);               \
+        TAILQ_NEXT((listelm), field) = (elm);                           \
+        (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field);          \
+    } while (0)
+
+#define TAILQ_INSERT_TAIL(head, elm, field) do {                        \
+        TAILQ_NEXT((elm), field) = NULL;                                \
+        (elm)->field.tqe_prev = (head)->tqh_last;                       \
+        *(head)->tqh_last = (elm);                                      \
+        (head)->tqh_last = &TAILQ_NEXT((elm), field);                   \
+    } while (0)
+
+#define TAILQ_CONCAT(head1, head2, field) do {                          \
+        if (!TAILQ_EMPTY(head2)) {                                      \
+            *(head1)->tqh_last = (head2)->tqh_first;                    \
+            (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last;     \
+            (head1)->tqh_last = (head2)->tqh_last;                      \
+            TAILQ_INIT((head2));                                        \
+        }                                                               \
+    } while (0)
+
+#define TAILQ_REMOVE(head, elm, field) do {                             \
+        if ((TAILQ_NEXT((elm), field)) != NULL)                         \
+            TAILQ_NEXT((elm), field)->field.tqe_prev =                  \
+                (elm)->field.tqe_prev;                                  \
+        else                                                            \
+            (head)->tqh_last = (elm)->field.tqe_prev;                   \
+        *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field);              \
+    } while (0)
+
+#define TAILQ_SWAP(head1, head2, type, field) do {                      \
+        struct type *swap_first = (head1)->tqh_first;                   \
+        struct type **swap_last = (head1)->tqh_last;                    \
+        (head1)->tqh_first = (head2)->tqh_first;                        \
+        (head1)->tqh_last = (head2)->tqh_last;                          \
+        (head2)->tqh_first = swap_first;                                \
+        (head2)->tqh_last = swap_last;                                  \
+        if ((swap_first = (head1)->tqh_first) != NULL)                  \
+            swap_first->field.tqe_prev = &(head1)->tqh_first;           \
+        else                                                            \
+            (head1)->tqh_last = &(head1)->tqh_first;                    \
+        if ((swap_first = (head2)->tqh_first) != NULL)                  \
+            swap_first->field.tqe_prev = &(head2)->tqh_first;           \
+        else                                                            \
+            (head2)->tqh_last = &(head2)->tqh_first;                    \
+    } while (0)
+
+#endif
diff --git a/app/src/main/cpp/shadowhook/third_party/bsd/tree.h b/app/src/main/cpp/shadowhook/third_party/bsd/tree.h
new file mode 100644
index 0000000000000000000000000000000000000000..e6db6a908df40a9e8874e99342b87e75108a8cad
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/third_party/bsd/tree.h
@@ -0,0 +1,759 @@
+/*-
+ * Copyright 2002 Niels Provos <provos@citi.umich.edu>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TREE_H
+#define TREE_H
+
+/*
+ * This file defines data structures for different types of trees:
+ * splay trees and red-black trees.
+ *
+ * A splay tree is a self-organizing data structure.  Every operation
+ * on the tree causes a splay to happen.  The splay moves the requested
+ * node to the root of the tree and partly rebalances it.
+ *
+ * This has the benefit that request locality causes faster lookups as
+ * the requested nodes move to the top of the tree.  On the other hand,
+ * every lookup causes memory writes.
+ *
+ * The Balance Theorem bounds the total access time for m operations
+ * and n inserts on an initially empty tree as O((m + n)lg n).  The
+ * amortized cost for a sequence of m accesses to a splay tree is O(lg n);
+ *
+ * A red-black tree is a binary search tree with the node color as an
+ * extra attribute.  It fulfills a set of conditions:
+ *      - every search path from the root to a leaf consists of the
+ *        same number of black nodes,
+ *      - each red node (except for the root) has a black parent,
+ *      - each leaf node is black.
+ *
+ * Every operation on a red-black tree is bounded as O(lg n).
+ * The maximum height of a red-black tree is 2lg (n+1).
+ */
+
+#define SPLAY_HEAD(name, type)                                          \
+struct name {                                                           \
+        struct type *sph_root; /* root of the tree */                   \
+}
+
+#define SPLAY_INITIALIZER(root)                                         \
+        { NULL }
+
+#define SPLAY_INIT(root) do {                                           \
+        (root)->sph_root = NULL;                                        \
+} while (/*CONSTCOND*/ 0)
+
+#define SPLAY_ENTRY(type)                                               \
+struct {                                                                \
+        struct type *spe_left; /* left element */                       \
+        struct type *spe_right; /* right element */                     \
+}
+
+#define SPLAY_LEFT(elm, field)          (elm)->field.spe_left
+#define SPLAY_RIGHT(elm, field)         (elm)->field.spe_right
+#define SPLAY_ROOT(head)                (head)->sph_root
+#define SPLAY_EMPTY(head)               (SPLAY_ROOT(head) == NULL)
+
+/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */
+#define SPLAY_ROTATE_RIGHT(head, tmp, field) do {                       \
+        SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field);  \
+        SPLAY_RIGHT(tmp, field) = (head)->sph_root;                     \
+        (head)->sph_root = tmp;                                         \
+} while (/*CONSTCOND*/ 0)
+        
+#define SPLAY_ROTATE_LEFT(head, tmp, field) do {                        \
+        SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field);  \
+        SPLAY_LEFT(tmp, field) = (head)->sph_root;                      \
+        (head)->sph_root = tmp;                                         \
+} while (/*CONSTCOND*/ 0)
+
+#define SPLAY_LINKLEFT(head, tmp, field) do {                           \
+        SPLAY_LEFT(tmp, field) = (head)->sph_root;                      \
+        tmp = (head)->sph_root;                                         \
+        (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);         \
+} while (/*CONSTCOND*/ 0)
+
+#define SPLAY_LINKRIGHT(head, tmp, field) do {                          \
+        SPLAY_RIGHT(tmp, field) = (head)->sph_root;                     \
+        tmp = (head)->sph_root;                                         \
+        (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);        \
+} while (/*CONSTCOND*/ 0)
+
+#define SPLAY_ASSEMBLE(head, node, left, right, field) do {             \
+        SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \
+        SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\
+        SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \
+        SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \
+} while (/*CONSTCOND*/ 0)
+
+/* Generates prototypes and inline functions */
+
+#define SPLAY_PROTOTYPE(name, type, field, cmp)                         \
+void name##_SPLAY(struct name *, struct type *);                        \
+void name##_SPLAY_MINMAX(struct name *, int);                           \
+struct type *name##_SPLAY_INSERT(struct name *, struct type *);         \
+struct type *name##_SPLAY_REMOVE(struct name *, struct type *);         \
+                                                                        \
+/* Finds the node with the same key as elm */                           \
+static __inline struct type *                                           \
+name##_SPLAY_FIND(struct name *head, struct type *elm)                  \
+{                                                                       \
+        if (SPLAY_EMPTY(head))                                          \
+                return(NULL);                                           \
+        name##_SPLAY(head, elm);                                        \
+        if ((cmp)(elm, (head)->sph_root) == 0)                          \
+                return (head->sph_root);                                \
+        return (NULL);                                                  \
+}                                                                       \
+                                                                        \
+static __inline struct type *                                          \
+name##_SPLAY_NEXT(struct name *head, struct type *elm)                 \
+{                                                                       \
+        name##_SPLAY(head, elm);                                        \
+        if (SPLAY_RIGHT(elm, field) != NULL) {                          \
+                elm = SPLAY_RIGHT(elm, field);                          \
+                while (SPLAY_LEFT(elm, field) != NULL) {                \
+                        elm = SPLAY_LEFT(elm, field);                   \
+                }                                                       \
+        } else                                                          \
+                elm = NULL;                                             \
+        return (elm);                                                   \
+}                                                                       \
+                                                                        \
+static __inline struct type *                                           \
+name##_SPLAY_MIN_MAX(struct name *head, int val)                        \
+{                                                                       \
+        name##_SPLAY_MINMAX(head, val);                                 \
+        return (SPLAY_ROOT(head));                                      \
+}
+
+/* Main splay operation.
+ * Moves node close to the key of elm to top
+ */
+#define SPLAY_GENERATE(name, type, field, cmp)                          \
+struct type *                                                           \
+name##_SPLAY_INSERT(struct name *head, struct type *elm)                \
+{                                                                       \
+    if (SPLAY_EMPTY(head)) {                                            \
+            SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL;    \
+    } else {                                                            \
+            int __comp;                                                 \
+            name##_SPLAY(head, elm);                                    \
+            __comp = (cmp)(elm, (head)->sph_root);                      \
+            if(__comp < 0) {                                            \
+                    SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\
+                    SPLAY_RIGHT(elm, field) = (head)->sph_root;         \
+                    SPLAY_LEFT((head)->sph_root, field) = NULL;         \
+            } else if (__comp > 0) {                                    \
+                    SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\
+                    SPLAY_LEFT(elm, field) = (head)->sph_root;          \
+                    SPLAY_RIGHT((head)->sph_root, field) = NULL;        \
+            } else                                                      \
+                    return ((head)->sph_root);                          \
+    }                                                                   \
+    (head)->sph_root = (elm);                                           \
+    return (NULL);                                                      \
+}                                                                       \
+                                                                        \
+struct type *                                                           \
+name##_SPLAY_REMOVE(struct name *head, struct type *elm)                \
+{                                                                       \
+        struct type *__tmp;                                             \
+        if (SPLAY_EMPTY(head))                                          \
+                return (NULL);                                          \
+        name##_SPLAY(head, elm);                                        \
+        if ((cmp)(elm, (head)->sph_root) == 0) {                        \
+                if (SPLAY_LEFT((head)->sph_root, field) == NULL) {      \
+                        (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\
+                } else {                                                \
+                        __tmp = SPLAY_RIGHT((head)->sph_root, field);   \
+                        (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\
+                        name##_SPLAY(head, elm);                        \
+                        SPLAY_RIGHT((head)->sph_root, field) = __tmp;   \
+                }                                                       \
+                return (elm);                                           \
+        }                                                               \
+        return (NULL);                                                  \
+}                                                                       \
+                                                                        \
+void                                                                    \
+name##_SPLAY(struct name *head, struct type *elm)                       \
+{                                                                       \
+        struct type __node, *__left, *__right, *__tmp;                  \
+        int __comp;                                                     \
+\
+        SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
+        __left = __right = &__node;                                     \
+\
+        while ((__comp = (cmp)(elm, (head)->sph_root)) != 0) {          \
+                if (__comp < 0) {                                       \
+                        __tmp = SPLAY_LEFT((head)->sph_root, field);    \
+                        if (__tmp == NULL)                              \
+                                break;                                  \
+                        if ((cmp)(elm, __tmp) < 0){                     \
+                                SPLAY_ROTATE_RIGHT(head, __tmp, field); \
+                                if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
+                                        break;                          \
+                        }                                               \
+                        SPLAY_LINKLEFT(head, __right, field);           \
+                } else if (__comp > 0) {                                \
+                        __tmp = SPLAY_RIGHT((head)->sph_root, field);   \
+                        if (__tmp == NULL)                              \
+                                break;                                  \
+                        if ((cmp)(elm, __tmp) > 0){                     \
+                                SPLAY_ROTATE_LEFT(head, __tmp, field);  \
+                                if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
+                                        break;                          \
+                        }                                               \
+                        SPLAY_LINKRIGHT(head, __left, field);           \
+                }                                                       \
+        }                                                               \
+        SPLAY_ASSEMBLE(head, &__node, __left, __right, field);          \
+}                                                                       \
+                                                                        \
+/* Splay with either the minimum or the maximum element                 \
+ * Used to find minimum or maximum element in tree.                     \
+ */                                                                     \
+void name##_SPLAY_MINMAX(struct name *head, int __comp) \
+{                                                                       \
+        struct type __node, *__left, *__right, *__tmp;                  \
+\
+        SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
+        __left = __right = &__node;                                     \
+\
+        while (1) {                                                     \
+                if (__comp < 0) {                                       \
+                        __tmp = SPLAY_LEFT((head)->sph_root, field);    \
+                        if (__tmp == NULL)                              \
+                                break;                                  \
+                        if (__comp < 0){                                \
+                                SPLAY_ROTATE_RIGHT(head, __tmp, field); \
+                                if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
+                                        break;                          \
+                        }                                               \
+                        SPLAY_LINKLEFT(head, __right, field);           \
+                } else if (__comp > 0) {                                \
+                        __tmp = SPLAY_RIGHT((head)->sph_root, field);   \
+                        if (__tmp == NULL)                              \
+                                break;                                  \
+                        if (__comp > 0) {                               \
+                                SPLAY_ROTATE_LEFT(head, __tmp, field);  \
+                                if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
+                                        break;                          \
+                        }                                               \
+                        SPLAY_LINKRIGHT(head, __left, field);           \
+                }                                                       \
+        }                                                               \
+        SPLAY_ASSEMBLE(head, &__node, __left, __right, field);          \
+}
+
+#define SPLAY_NEGINF    -1
+#define SPLAY_INF       1
+
+#define SPLAY_INSERT(name, x, y)        name##_SPLAY_INSERT(x, y)
+#define SPLAY_REMOVE(name, x, y)        name##_SPLAY_REMOVE(x, y)
+#define SPLAY_FIND(name, x, y)          name##_SPLAY_FIND(x, y)
+#define SPLAY_NEXT(name, x, y)          name##_SPLAY_NEXT(x, y)
+#define SPLAY_MIN(name, x)              (SPLAY_EMPTY(x) ? NULL  \
+                                        : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF))
+#define SPLAY_MAX(name, x)              (SPLAY_EMPTY(x) ? NULL  \
+                                        : name##_SPLAY_MIN_MAX(x, SPLAY_INF))
+
+#define SPLAY_FOREACH(x, name, head)                                    \
+        for ((x) = SPLAY_MIN(name, head);                               \
+             (x) != NULL;                                               \
+             (x) = SPLAY_NEXT(name, head, x))
+
+/* Macros that define a red-black tree */
+#define RB_HEAD(name, type)                                             \
+struct name {                                                           \
+        struct type *rbh_root; /* root of the tree */                   \
+}
+
+#define RB_INITIALIZER(root)                                            \
+        { NULL }
+
+#define RB_INIT(root) do {                                              \
+        (root)->rbh_root = NULL;                                        \
+} while (/*CONSTCOND*/ 0)
+
+#define RB_BLACK        0
+#define RB_RED          1
+#define RB_ENTRY(type)                                                  \
+struct {                                                                \
+        struct type *rbe_left;          /* left element */              \
+        struct type *rbe_right;         /* right element */             \
+        struct type *rbe_parent;        /* parent element */            \
+        int rbe_color;                  /* node color */                \
+}
+
+#define RB_LEFT(elm, field)             (elm)->field.rbe_left
+#define RB_RIGHT(elm, field)            (elm)->field.rbe_right
+#define RB_PARENT(elm, field)           (elm)->field.rbe_parent
+#define RB_COLOR(elm, field)            (elm)->field.rbe_color
+#define RB_ROOT(head)                   (head)->rbh_root
+#define RB_EMPTY(head)                  (RB_ROOT(head) == NULL)
+
+#define RB_SET(elm, parent, field) do {                                 \
+        RB_PARENT(elm, field) = parent;                                 \
+        RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL;              \
+        RB_COLOR(elm, field) = RB_RED;                                  \
+} while (/*CONSTCOND*/ 0)
+
+#define RB_SET_BLACKRED(black, red, field) do {                         \
+        RB_COLOR(black, field) = RB_BLACK;                              \
+        RB_COLOR(red, field) = RB_RED;                                  \
+} while (/*CONSTCOND*/ 0)
+
+#ifndef RB_AUGMENT
+#define RB_AUGMENT(x)   do {} while (0)
+#endif
+
+#define RB_ROTATE_LEFT(head, elm, tmp, field) do {                      \
+        (tmp) = RB_RIGHT(elm, field);                                   \
+        if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field)) != NULL) {     \
+                RB_PARENT(RB_LEFT(tmp, field), field) = (elm);          \
+        }                                                               \
+        RB_AUGMENT(elm);                                                \
+        if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) {  \
+                if ((elm) == RB_LEFT(RB_PARENT(elm, field), field))     \
+                        RB_LEFT(RB_PARENT(elm, field), field) = (tmp);  \
+                else                                                    \
+                        RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \
+        } else                                                          \
+                (head)->rbh_root = (tmp);                               \
+        RB_LEFT(tmp, field) = (elm);                                    \
+        RB_PARENT(elm, field) = (tmp);                                  \
+        RB_AUGMENT(tmp);                                                \
+        if ((RB_PARENT(tmp, field)))                                    \
+                RB_AUGMENT(RB_PARENT(tmp, field));                      \
+} while (/*CONSTCOND*/ 0)
+
+#define RB_ROTATE_RIGHT(head, elm, tmp, field) do {                     \
+        (tmp) = RB_LEFT(elm, field);                                    \
+        if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field)) != NULL) {     \
+                RB_PARENT(RB_RIGHT(tmp, field), field) = (elm);         \
+        }                                                               \
+        RB_AUGMENT(elm);                                                \
+        if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) {  \
+                if ((elm) == RB_LEFT(RB_PARENT(elm, field), field))     \
+                        RB_LEFT(RB_PARENT(elm, field), field) = (tmp);  \
+                else                                                    \
+                        RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \
+        } else                                                          \
+                (head)->rbh_root = (tmp);                               \
+        RB_RIGHT(tmp, field) = (elm);                                   \
+        RB_PARENT(elm, field) = (tmp);                                  \
+        RB_AUGMENT(tmp);                                                \
+        if ((RB_PARENT(tmp, field)))                                    \
+                RB_AUGMENT(RB_PARENT(tmp, field));                      \
+} while (/*CONSTCOND*/ 0)
+
+/* Generates prototypes and inline functions */
+#define RB_PROTOTYPE(name, type, field, cmp)                            \
+        RB_PROTOTYPE_INTERNAL(name, type, field, cmp,)
+#define RB_PROTOTYPE_STATIC(name, type, field, cmp)                     \
+        RB_PROTOTYPE_INTERNAL(name, type, field, cmp, static)
+#define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr)             \
+attr void name##_RB_INSERT_COLOR(struct name *, struct type *);         \
+attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *);\
+attr struct type *name##_RB_REMOVE(struct name *, struct type *);       \
+attr struct type *name##_RB_INSERT(struct name *, struct type *);       \
+attr struct type *name##_RB_FIND(struct name *, struct type *);         \
+attr struct type *name##_RB_NFIND(struct name *, struct type *);        \
+attr struct type *name##_RB_NEXT(struct type *);                        \
+attr struct type *name##_RB_PREV(struct type *);                        \
+attr struct type *name##_RB_MINMAX(struct name *, int);                 \
+                                                                        \
+
+/* Main rb operation.
+ * Moves node close to the key of elm to top
+ */
+#define RB_GENERATE(name, type, field, cmp)                             \
+        RB_GENERATE_INTERNAL(name, type, field, cmp,)
+#define RB_GENERATE_STATIC(name, type, field, cmp)                      \
+        RB_GENERATE_INTERNAL(name, type, field, cmp, static)
+#define RB_GENERATE_INTERNAL(name, type, field, cmp, attr)              \
+attr void                                                               \
+name##_RB_INSERT_COLOR(struct name *head, struct type *elm)             \
+{                                                                       \
+        struct type *parent, *gparent, *tmp;                            \
+        while ((parent = RB_PARENT(elm, field)) != NULL &&              \
+            RB_COLOR(parent, field) == RB_RED) {                        \
+                gparent = RB_PARENT(parent, field);                     \
+                if (parent == RB_LEFT(gparent, field)) {                \
+                        tmp = RB_RIGHT(gparent, field);                 \
+                        if (tmp && RB_COLOR(tmp, field) == RB_RED) {    \
+                                RB_COLOR(tmp, field) = RB_BLACK;        \
+                                RB_SET_BLACKRED(parent, gparent, field);\
+                                elm = gparent;                          \
+                                continue;                               \
+                        }                                               \
+                        if (RB_RIGHT(parent, field) == elm) {           \
+                                RB_ROTATE_LEFT(head, parent, tmp, field);\
+                                tmp = parent;                           \
+                                parent = elm;                           \
+                                elm = tmp;                              \
+                        }                                               \
+                        RB_SET_BLACKRED(parent, gparent, field);        \
+                        RB_ROTATE_RIGHT(head, gparent, tmp, field);     \
+                } else {                                                \
+                        tmp = RB_LEFT(gparent, field);                  \
+                        if (tmp && RB_COLOR(tmp, field) == RB_RED) {    \
+                                RB_COLOR(tmp, field) = RB_BLACK;        \
+                                RB_SET_BLACKRED(parent, gparent, field);\
+                                elm = gparent;                          \
+                                continue;                               \
+                        }                                               \
+                        if (RB_LEFT(parent, field) == elm) {            \
+                                RB_ROTATE_RIGHT(head, parent, tmp, field);\
+                                tmp = parent;                           \
+                                parent = elm;                           \
+                                elm = tmp;                              \
+                        }                                               \
+                        RB_SET_BLACKRED(parent, gparent, field);        \
+                        RB_ROTATE_LEFT(head, gparent, tmp, field);      \
+                }                                                       \
+        }                                                               \
+        RB_COLOR(head->rbh_root, field) = RB_BLACK;                     \
+}                                                                       \
+                                                                        \
+attr void                                                               \
+name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \
+{                                                                       \
+        struct type *tmp;                                               \
+        while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) &&     \
+            elm != RB_ROOT(head)) {                                     \
+                if (RB_LEFT(parent, field) == elm) {                    \
+                        tmp = RB_RIGHT(parent, field);                  \
+                        if (RB_COLOR(tmp, field) == RB_RED) {           \
+                                RB_SET_BLACKRED(tmp, parent, field);    \
+                                RB_ROTATE_LEFT(head, parent, tmp, field);\
+                                tmp = RB_RIGHT(parent, field);          \
+                        }                                               \
+                        if ((RB_LEFT(tmp, field) == NULL ||             \
+                            RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
+                            (RB_RIGHT(tmp, field) == NULL ||            \
+                            RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
+                                RB_COLOR(tmp, field) = RB_RED;          \
+                                elm = parent;                           \
+                                parent = RB_PARENT(elm, field);         \
+                        } else {                                        \
+                                if (RB_RIGHT(tmp, field) == NULL ||     \
+                                    RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\
+                                        struct type *oleft;             \
+                                        if ((oleft = RB_LEFT(tmp, field)) \
+                                            != NULL)                    \
+                                                RB_COLOR(oleft, field) = RB_BLACK;\
+                                        RB_COLOR(tmp, field) = RB_RED;  \
+                                        RB_ROTATE_RIGHT(head, tmp, oleft, field);\
+                                        tmp = RB_RIGHT(parent, field);  \
+                                }                                       \
+                                RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
+                                RB_COLOR(parent, field) = RB_BLACK;     \
+                                if (RB_RIGHT(tmp, field))               \
+                                        RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\
+                                RB_ROTATE_LEFT(head, parent, tmp, field);\
+                                elm = RB_ROOT(head);                    \
+                                break;                                  \
+                        }                                               \
+                } else {                                                \
+                        tmp = RB_LEFT(parent, field);                   \
+                        if (RB_COLOR(tmp, field) == RB_RED) {           \
+                                RB_SET_BLACKRED(tmp, parent, field);    \
+                                RB_ROTATE_RIGHT(head, parent, tmp, field);\
+                                tmp = RB_LEFT(parent, field);           \
+                        }                                               \
+                        if ((RB_LEFT(tmp, field) == NULL ||             \
+                            RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
+                            (RB_RIGHT(tmp, field) == NULL ||            \
+                            RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
+                                RB_COLOR(tmp, field) = RB_RED;          \
+                                elm = parent;                           \
+                                parent = RB_PARENT(elm, field);         \
+                        } else {                                        \
+                                if (RB_LEFT(tmp, field) == NULL ||      \
+                                    RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\
+                                        struct type *oright;            \
+                                        if ((oright = RB_RIGHT(tmp, field)) \
+                                            != NULL)                    \
+                                                RB_COLOR(oright, field) = RB_BLACK;\
+                                        RB_COLOR(tmp, field) = RB_RED;  \
+                                        RB_ROTATE_LEFT(head, tmp, oright, field);\
+                                        tmp = RB_LEFT(parent, field);   \
+                                }                                       \
+                                RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
+                                RB_COLOR(parent, field) = RB_BLACK;     \
+                                if (RB_LEFT(tmp, field))                \
+                                        RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\
+                                RB_ROTATE_RIGHT(head, parent, tmp, field);\
+                                elm = RB_ROOT(head);                    \
+                                break;                                  \
+                        }                                               \
+                }                                                       \
+        }                                                               \
+        if (elm)                                                        \
+                RB_COLOR(elm, field) = RB_BLACK;                        \
+}                                                                       \
+                                                                        \
+attr struct type *                                                      \
+name##_RB_REMOVE(struct name *head, struct type *elm)                   \
+{                                                                       \
+        struct type *child, *parent, *old = elm;                        \
+        int color;                                                      \
+        if (RB_LEFT(elm, field) == NULL)                                \
+                child = RB_RIGHT(elm, field);                           \
+        else if (RB_RIGHT(elm, field) == NULL)                          \
+                child = RB_LEFT(elm, field);                            \
+        else {                                                          \
+                struct type *left;                                      \
+                elm = RB_RIGHT(elm, field);                             \
+                while ((left = RB_LEFT(elm, field)) != NULL)            \
+                        elm = left;                                     \
+                child = RB_RIGHT(elm, field);                           \
+                parent = RB_PARENT(elm, field);                         \
+                color = RB_COLOR(elm, field);                           \
+                if (child)                                              \
+                        RB_PARENT(child, field) = parent;               \
+                if (parent) {                                           \
+                        if (RB_LEFT(parent, field) == elm)              \
+                                RB_LEFT(parent, field) = child;         \
+                        else                                            \
+                                RB_RIGHT(parent, field) = child;        \
+                        RB_AUGMENT(parent);                             \
+                } else                                                  \
+                        RB_ROOT(head) = child;                          \
+                if (RB_PARENT(elm, field) == old)                       \
+                        parent = elm;                                   \
+                (elm)->field = (old)->field;                            \
+                if (RB_PARENT(old, field)) {                            \
+                        if (RB_LEFT(RB_PARENT(old, field), field) == old)\
+                                RB_LEFT(RB_PARENT(old, field), field) = elm;\
+                        else                                            \
+                                RB_RIGHT(RB_PARENT(old, field), field) = elm;\
+                        RB_AUGMENT(RB_PARENT(old, field));              \
+                } else                                                  \
+                        RB_ROOT(head) = elm;                            \
+                RB_PARENT(RB_LEFT(old, field), field) = elm;            \
+                if (RB_RIGHT(old, field))                               \
+                        RB_PARENT(RB_RIGHT(old, field), field) = elm;   \
+                if (parent) {                                           \
+                        left = parent;                                  \
+                        do {                                            \
+                                RB_AUGMENT(left);                       \
+                        } while ((left = RB_PARENT(left, field)) != NULL); \
+                }                                                       \
+                goto color;                                             \
+        }                                                               \
+        parent = RB_PARENT(elm, field);                                 \
+        color = RB_COLOR(elm, field);                                   \
+        if (child)                                                      \
+                RB_PARENT(child, field) = parent;                       \
+        if (parent) {                                                   \
+                if (RB_LEFT(parent, field) == elm)                      \
+                        RB_LEFT(parent, field) = child;                 \
+                else                                                    \
+                        RB_RIGHT(parent, field) = child;                \
+                RB_AUGMENT(parent);                                     \
+        } else                                                          \
+                RB_ROOT(head) = child;                                  \
+color:                                                                  \
+        if (color == RB_BLACK)                                          \
+                name##_RB_REMOVE_COLOR(head, parent, child);            \
+        return (old);                                                   \
+}                                                                       \
+                                                                        \
+/* Inserts a node into the RB tree */                                   \
+attr struct type *                                                      \
+name##_RB_INSERT(struct name *head, struct type *elm)                   \
+{                                                                       \
+        struct type *tmp;                                               \
+        struct type *parent = NULL;                                     \
+        int comp = 0;                                                   \
+        tmp = RB_ROOT(head);                                            \
+        while (tmp) {                                                   \
+                parent = tmp;                                           \
+                comp = (cmp)(elm, parent);                              \
+                if (comp < 0)                                           \
+                        tmp = RB_LEFT(tmp, field);                      \
+                else if (comp > 0)                                      \
+                        tmp = RB_RIGHT(tmp, field);                     \
+                else                                                    \
+                        return (tmp);                                   \
+        }                                                               \
+        RB_SET(elm, parent, field);                                     \
+        if (parent != NULL) {                                           \
+                if (comp < 0)                                           \
+                        RB_LEFT(parent, field) = elm;                   \
+                else                                                    \
+                        RB_RIGHT(parent, field) = elm;                  \
+                RB_AUGMENT(parent);                                     \
+        } else                                                          \
+                RB_ROOT(head) = elm;                                    \
+        name##_RB_INSERT_COLOR(head, elm);                              \
+        return (NULL);                                                  \
+}                                                                       \
+                                                                        \
+/* Finds the node with the same key as elm */                           \
+attr struct type *                                                      \
+name##_RB_FIND(struct name *head, struct type *elm)                     \
+{                                                                       \
+        struct type *tmp = RB_ROOT(head);                               \
+        int comp;                                                       \
+        while (tmp) {                                                   \
+                comp = cmp(elm, tmp);                                   \
+                if (comp < 0)                                           \
+                        tmp = RB_LEFT(tmp, field);                      \
+                else if (comp > 0)                                      \
+                        tmp = RB_RIGHT(tmp, field);                     \
+                else                                                    \
+                        return (tmp);                                   \
+        }                                                               \
+        return (NULL);                                                  \
+}                                                                       \
+                                                                        \
+/* Finds the first node greater than or equal to the search key */      \
+attr struct type *                                                      \
+name##_RB_NFIND(struct name *head, struct type *elm)                    \
+{                                                                       \
+        struct type *tmp = RB_ROOT(head);                               \
+        struct type *res = NULL;                                        \
+        int comp;                                                       \
+        while (tmp) {                                                   \
+                comp = cmp(elm, tmp);                                   \
+                if (comp < 0) {                                         \
+                        res = tmp;                                      \
+                        tmp = RB_LEFT(tmp, field);                      \
+                }                                                       \
+                else if (comp > 0)                                      \
+                        tmp = RB_RIGHT(tmp, field);                     \
+                else                                                    \
+                        return (tmp);                                   \
+        }                                                               \
+        return (res);                                                   \
+}                                                                       \
+                                                                        \
+/* ARGSUSED */                                                          \
+attr struct type *                                                      \
+name##_RB_NEXT(struct type *elm)                                        \
+{                                                                       \
+        if (RB_RIGHT(elm, field)) {                                     \
+                elm = RB_RIGHT(elm, field);                             \
+                while (RB_LEFT(elm, field))                             \
+                        elm = RB_LEFT(elm, field);                      \
+        } else {                                                        \
+                if (RB_PARENT(elm, field) &&                            \
+                    (elm == RB_LEFT(RB_PARENT(elm, field), field)))     \
+                        elm = RB_PARENT(elm, field);                    \
+                else {                                                  \
+                        while (RB_PARENT(elm, field) &&                 \
+                            (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\
+                                elm = RB_PARENT(elm, field);            \
+                        elm = RB_PARENT(elm, field);                    \
+                }                                                       \
+        }                                                               \
+        return (elm);                                                   \
+}                                                                       \
+                                                                        \
+/* ARGSUSED */                                                          \
+attr struct type *                                                      \
+name##_RB_PREV(struct type *elm)                                        \
+{                                                                       \
+        if (RB_LEFT(elm, field)) {                                      \
+                elm = RB_LEFT(elm, field);                              \
+                while (RB_RIGHT(elm, field))                            \
+                        elm = RB_RIGHT(elm, field);                     \
+        } else {                                                        \
+                if (RB_PARENT(elm, field) &&                            \
+                    (elm == RB_RIGHT(RB_PARENT(elm, field), field)))    \
+                        elm = RB_PARENT(elm, field);                    \
+                else {                                                  \
+                        while (RB_PARENT(elm, field) &&                 \
+                            (elm == RB_LEFT(RB_PARENT(elm, field), field)))\
+                                elm = RB_PARENT(elm, field);            \
+                        elm = RB_PARENT(elm, field);                    \
+                }                                                       \
+        }                                                               \
+        return (elm);                                                   \
+}                                                                       \
+                                                                        \
+attr struct type *                                                      \
+name##_RB_MINMAX(struct name *head, int val)                            \
+{                                                                       \
+        struct type *tmp = RB_ROOT(head);                               \
+        struct type *parent = NULL;                                     \
+        while (tmp) {                                                   \
+                parent = tmp;                                           \
+                if (val < 0)                                            \
+                        tmp = RB_LEFT(tmp, field);                      \
+                else                                                    \
+                        tmp = RB_RIGHT(tmp, field);                     \
+        }                                                               \
+        return (parent);                                                \
+}
+
+#define RB_NEGINF       -1
+#define RB_INF  1
+
+#define RB_INSERT(name, x, y)   name##_RB_INSERT(x, y)
+#define RB_REMOVE(name, x, y)   name##_RB_REMOVE(x, y)
+#define RB_FIND(name, x, y)     name##_RB_FIND(x, y)
+#define RB_NFIND(name, x, y)    name##_RB_NFIND(x, y)
+#define RB_NEXT(name, x, y)     name##_RB_NEXT(y)
+#define RB_PREV(name, x, y)     name##_RB_PREV(y)
+#define RB_MIN(name, x)         name##_RB_MINMAX(x, RB_NEGINF)
+#define RB_MAX(name, x)         name##_RB_MINMAX(x, RB_INF)
+
+#define RB_FOREACH(x, name, head)                                       \
+        for ((x) = RB_MIN(name, head);                                  \
+             (x) != NULL;                                               \
+             (x) = name##_RB_NEXT(x))
+
+#define RB_FOREACH_FROM(x, name, y)                                     \
+        for ((x) = (y);                                                 \
+            ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL);    \
+             (x) = (y))
+
+#define RB_FOREACH_SAFE(x, name, head, y)                               \
+        for ((x) = RB_MIN(name, head);                                  \
+            ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL);    \
+             (x) = (y))
+
+#define RB_FOREACH_REVERSE(x, name, head)                               \
+        for ((x) = RB_MAX(name, head);                                  \
+             (x) != NULL;                                               \
+             (x) = name##_RB_PREV(x))
+
+#define RB_FOREACH_REVERSE_FROM(x, name, y)                             \
+        for ((x) = (y);                                                 \
+            ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL);    \
+             (x) = (y))
+
+#define RB_FOREACH_REVERSE_SAFE(x, name, head, y)                       \
+        for ((x) = RB_MAX(name, head);                                  \
+            ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL);    \
+             (x) = (y))
+
+#endif
diff --git a/app/src/main/cpp/shadowhook/third_party/lss/LICENSE b/app/src/main/cpp/shadowhook/third_party/lss/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..58ab3fba9d4c3318ea545f451bb3ecbfdd5d8f6f
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/third_party/lss/LICENSE
@@ -0,0 +1,28 @@
+Copyright (c) 2005-2011, Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/app/src/main/cpp/shadowhook/third_party/lss/linux_syscall_support.h b/app/src/main/cpp/shadowhook/third_party/lss/linux_syscall_support.h
new file mode 100644
index 0000000000000000000000000000000000000000..d3791cd4e9c802c157dd8ddfb6d0d9803a9b3d22
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/third_party/lss/linux_syscall_support.h
@@ -0,0 +1,4867 @@
+/* Copyright (c) 2005-2011, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ---
+ * Author: Markus Gutschke
+ */
+
+/* This file includes Linux-specific support functions common to the
+ * coredumper and the thread lister; primarily, this is a collection
+ * of direct system calls, and a couple of symbols missing from
+ * standard header files.
+ * There are a few options that the including file can set to control
+ * the behavior of this file:
+ *
+ * SYS_CPLUSPLUS:
+ *   The entire header file will normally be wrapped in 'extern "C" { }",
+ *   making it suitable for compilation as both C and C++ source. If you
+ *   do not want to do this, you can set the SYS_CPLUSPLUS macro to inhibit
+ *   the wrapping. N.B. doing so will suppress inclusion of all prerequisite
+ *   system header files, too. It is the caller's responsibility to provide
+ *   the necessary definitions.
+ *
+ * SYS_ERRNO:
+ *   All system calls will update "errno" unless overriden by setting the
+ *   SYS_ERRNO macro prior to including this file. SYS_ERRNO should be
+ *   an l-value.
+ *
+ * SYS_INLINE:
+ *   New symbols will be defined "static inline", unless overridden by
+ *   the SYS_INLINE macro.
+ *
+ * SYS_LINUX_SYSCALL_SUPPORT_H
+ *   This macro is used to avoid multiple inclusions of this header file.
+ *   If you need to include this file more than once, make sure to
+ *   unset SYS_LINUX_SYSCALL_SUPPORT_H before each inclusion.
+ *
+ * SYS_PREFIX:
+ *   New system calls will have a prefix of "sys_" unless overridden by
+ *   the SYS_PREFIX macro. Valid values for this macro are [0..9] which
+ *   results in prefixes "sys[0..9]_". It is also possible to set this
+ *   macro to -1, which avoids all prefixes.
+ *
+ * SYS_SYSCALL_ENTRYPOINT:
+ *   Some applications (such as sandboxes that filter system calls), need
+ *   to be able to run custom-code each time a system call is made. If this
+ *   macro is defined, it expands to the name of a "common" symbol. If
+ *   this symbol is assigned a non-NULL pointer value, it is used as the
+ *   address of the system call entrypoint.
+ *   A pointer to this symbol can be obtained by calling
+ *   get_syscall_entrypoint()
+ *
+ * This file defines a few internal symbols that all start with "LSS_".
+ * Do not access these symbols from outside this file. They are not part
+ * of the supported API.
+ */
+#ifndef SYS_LINUX_SYSCALL_SUPPORT_H
+#define SYS_LINUX_SYSCALL_SUPPORT_H
+
+/* We currently only support x86-32, x86-64, ARM, MIPS, PPC, s390 and s390x
+ * on Linux.
+ * Porting to other related platforms should not be difficult.
+ */
+#if (defined(__i386__) || defined(__x86_64__) || defined(__ARM_ARCH_3__) ||   \
+     defined(__mips__) || defined(__PPC__) || defined(__ARM_EABI__) || \
+     defined(__aarch64__) || defined(__s390__) || defined(__e2k__))  \
+  && (defined(__linux) || defined(__ANDROID__))
+
+#ifndef SYS_CPLUSPLUS
+#ifdef __cplusplus
+/* Some system header files in older versions of gcc neglect to properly
+ * handle being included from C++. As it appears to be harmless to have
+ * multiple nested 'extern "C"' blocks, just add another one here.
+ */
+extern "C" {
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/ptrace.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+#include <linux/unistd.h>
+#include <endian.h>
+
+#ifdef __mips__
+/* Include definitions of the ABI currently in use.                          */
+#ifdef __ANDROID__
+/* Android doesn't have sgidefs.h, but does have asm/sgidefs.h,
+ * which has the definitions we need.
+ */
+#include <asm/sgidefs.h>
+#else
+#include <sgidefs.h>
+#endif
+#endif
+#endif
+
+/* Some libcs, for example Android NDK and musl, #define these
+ * macros as aliases to their non-64 counterparts. To avoid naming
+ * conflict, remove them.
+ *
+ * These are restored by the corresponding #pragma pop_macro near
+ * the end of this file.
+ */
+#pragma push_macro("stat64")
+#pragma push_macro("fstat64")
+#pragma push_macro("lstat64")
+#pragma push_macro("pread64")
+#pragma push_macro("pwrite64")
+#pragma push_macro("getdents64")
+#undef stat64
+#undef fstat64
+#undef lstat64
+#undef pread64
+#undef pwrite64
+#undef getdents64
+
+#if defined(__ANDROID__) && defined(__x86_64__)
+// A number of x86_64 syscalls are blocked by seccomp on recent Android;
+// undefine them so that modern alternatives will be used instead where
+// possible.
+// The alternative syscalls have been sanity checked against linux-3.4+;
+// older versions might not work.
+# undef __NR_getdents
+# undef __NR_dup2
+# undef __NR_fork
+# undef __NR_getpgrp
+# undef __NR_open
+# undef __NR_poll
+# undef __NR_readlink
+# undef __NR_stat
+# undef __NR_unlink
+# undef __NR_pipe
+#endif
+
+#if defined(__ANDROID__)
+// waitpid is blocked by seccomp on all architectures on recent Android.
+# undef __NR_waitpid
+#endif
+
+/* As glibc often provides subtly incompatible data structures (and implicit
+ * wrapper functions that convert them), we provide our own kernel data
+ * structures for use by the system calls.
+ * These structures have been developed by using Linux 2.6.23 headers for
+ * reference. Note though, we do not care about exact API compatibility
+ * with the kernel, and in fact the kernel often does not have a single
+ * API that works across architectures. Instead, we try to mimic the glibc
+ * API where reasonable, and only guarantee ABI compatibility with the
+ * kernel headers.
+ * Most notably, here are a few changes that were made to the structures
+ * defined by kernel headers:
+ *
+ * - we only define structures, but not symbolic names for kernel data
+ *   types. For the latter, we directly use the native C datatype
+ *   (i.e. "unsigned" instead of "mode_t").
+ * - in a few cases, it is possible to define identical structures for
+ *   both 32bit (e.g. i386) and 64bit (e.g. x86-64) platforms by
+ *   standardizing on the 64bit version of the data types. In particular,
+ *   this means that we use "unsigned" where the 32bit headers say
+ *   "unsigned long".
+ * - overall, we try to minimize the number of cases where we need to
+ *   conditionally define different structures.
+ * - the "struct kernel_sigaction" class of structures have been
+ *   modified to more closely mimic glibc's API by introducing an
+ *   anonymous union for the function pointer.
+ * - a small number of field names had to have an underscore appended to
+ *   them, because glibc defines a global macro by the same name.
+ */
+
+/* include/linux/dirent.h                                                    */
+struct kernel_dirent64 {
+  unsigned long long d_ino;
+  long long          d_off;
+  unsigned short     d_reclen;
+  unsigned char      d_type;
+  char               d_name[256];
+};
+
+/* include/linux/dirent.h                                                    */
+#if !defined(__NR_getdents)
+// when getdents is not available, getdents64 is used for both.
+#define kernel_dirent kernel_dirent64
+#else
+struct kernel_dirent {
+  long               d_ino;
+  long               d_off;
+  unsigned short     d_reclen;
+  char               d_name[256];
+};
+#endif
+
+/* include/linux/uio.h                                                       */
+struct kernel_iovec {
+  void               *iov_base;
+  unsigned long      iov_len;
+};
+
+/* include/linux/socket.h                                                    */
+struct kernel_msghdr {
+  void               *msg_name;
+  int                msg_namelen;
+  struct kernel_iovec*msg_iov;
+  unsigned long      msg_iovlen;
+  void               *msg_control;
+  unsigned long      msg_controllen;
+  unsigned           msg_flags;
+};
+
+/* include/asm-generic/poll.h                                                */
+struct kernel_pollfd {
+  int                fd;
+  short              events;
+  short              revents;
+};
+
+/* include/linux/resource.h                                                  */
+struct kernel_rlimit {
+  unsigned long      rlim_cur;
+  unsigned long      rlim_max;
+};
+
+/* include/linux/time.h                                                      */
+struct kernel_timespec {
+  long               tv_sec;
+  long               tv_nsec;
+};
+
+/* include/linux/time.h                                                      */
+struct kernel_timeval {
+  long               tv_sec;
+  long               tv_usec;
+};
+
+/* include/linux/resource.h                                                  */
+struct kernel_rusage {
+  struct kernel_timeval ru_utime;
+  struct kernel_timeval ru_stime;
+  long               ru_maxrss;
+  long               ru_ixrss;
+  long               ru_idrss;
+  long               ru_isrss;
+  long               ru_minflt;
+  long               ru_majflt;
+  long               ru_nswap;
+  long               ru_inblock;
+  long               ru_oublock;
+  long               ru_msgsnd;
+  long               ru_msgrcv;
+  long               ru_nsignals;
+  long               ru_nvcsw;
+  long               ru_nivcsw;
+};
+
+#if defined(__i386__) || defined(__ARM_EABI__) || defined(__ARM_ARCH_3__) \
+  || defined(__PPC__) || (defined(__s390__) && !defined(__s390x__)) \
+  || defined(__e2k__)
+
+/* include/asm-{arm,i386,mips,ppc}/signal.h                                  */
+struct kernel_old_sigaction {
+  union {
+    void             (*sa_handler_)(int);
+    void             (*sa_sigaction_)(int, siginfo_t *, void *);
+  };
+  unsigned long      sa_mask;
+  unsigned long      sa_flags;
+  void               (*sa_restorer)(void);
+} __attribute__((packed,aligned(4)));
+#elif (defined(__mips__) && _MIPS_SIM == _MIPS_SIM_ABI32)
+  #define kernel_old_sigaction kernel_sigaction
+#elif defined(__aarch64__)
+  // No kernel_old_sigaction defined for arm64.
+#endif
+
+/* Some kernel functions (e.g. sigaction() in 2.6.23) require that the
+ * exactly match the size of the signal set, even though the API was
+ * intended to be extensible. We define our own KERNEL_NSIG to deal with
+ * this.
+ * Please note that glibc provides signals [1.._NSIG-1], whereas the
+ * kernel (and this header) provides the range [1..KERNEL_NSIG]. The
+ * actual number of signals is obviously the same, but the constants
+ * differ by one.
+ */
+#ifdef __mips__
+#define KERNEL_NSIG 128
+#else
+#define KERNEL_NSIG  64
+#endif
+
+/* include/asm-{arm,aarch64,i386,mips,x86_64}/signal.h                       */
+struct kernel_sigset_t {
+  unsigned long sig[(KERNEL_NSIG + 8*sizeof(unsigned long) - 1)/
+                    (8*sizeof(unsigned long))];
+};
+
+/* include/asm-{arm,i386,mips,x86_64,ppc}/signal.h                           */
+struct kernel_sigaction {
+#ifdef __mips__
+  unsigned long      sa_flags;
+  union {
+    void             (*sa_handler_)(int);
+    void             (*sa_sigaction_)(int, siginfo_t *, void *);
+  };
+  struct kernel_sigset_t sa_mask;
+#else
+  union {
+    void             (*sa_handler_)(int);
+    void             (*sa_sigaction_)(int, siginfo_t *, void *);
+  };
+  unsigned long      sa_flags;
+  void               (*sa_restorer)(void);
+  struct kernel_sigset_t sa_mask;
+#endif
+};
+
+/* include/linux/socket.h                                                    */
+struct kernel_sockaddr {
+  unsigned short     sa_family;
+  char               sa_data[14];
+};
+
+/* include/asm-{arm,aarch64,i386,mips,ppc,s390}/stat.h                       */
+#ifdef __mips__
+#if _MIPS_SIM == _MIPS_SIM_ABI64
+struct kernel_stat {
+#else
+struct kernel_stat64 {
+#endif
+  unsigned           st_dev;
+  unsigned           __pad0[3];
+  unsigned long long st_ino;
+  unsigned           st_mode;
+  unsigned           st_nlink;
+  unsigned           st_uid;
+  unsigned           st_gid;
+  unsigned           st_rdev;
+  unsigned           __pad1[3];
+  long long          st_size;
+  unsigned           st_atime_;
+  unsigned           st_atime_nsec_;
+  unsigned           st_mtime_;
+  unsigned           st_mtime_nsec_;
+  unsigned           st_ctime_;
+  unsigned           st_ctime_nsec_;
+  unsigned           st_blksize;
+  unsigned           __pad2;
+  unsigned long long st_blocks;
+};
+#elif defined __PPC__
+struct kernel_stat64 {
+  unsigned long long st_dev;
+  unsigned long long st_ino;
+  unsigned           st_mode;
+  unsigned           st_nlink;
+  unsigned           st_uid;
+  unsigned           st_gid;
+  unsigned long long st_rdev;
+  unsigned short int __pad2;
+  long long          st_size;
+  long               st_blksize;
+  long long          st_blocks;
+  long               st_atime_;
+  unsigned long      st_atime_nsec_;
+  long               st_mtime_;
+  unsigned long      st_mtime_nsec_;
+  long               st_ctime_;
+  unsigned long      st_ctime_nsec_;
+  unsigned long      __unused4;
+  unsigned long      __unused5;
+};
+#elif defined(__e2k__)
+struct kernel_stat64 {
+  unsigned long long st_dev;
+  unsigned long long st_ino;
+  unsigned int       st_mode;
+  unsigned int       st_nlink;
+  unsigned int       st_uid;
+  unsigned int       st_gid;
+  unsigned long long st_rdev;
+  long long          st_size;
+  int                st_blksize;
+  int                __pad2;
+  unsigned long long st_blocks;
+  int                st_atime_;
+  unsigned int       st_atime_nsec_;
+  int                st_mtime_;
+  unsigned int       st_mtime_nsec_;
+  int                st_ctime_;
+  unsigned int       st_ctime_nsec_;
+  unsigned int       __unused4;
+  unsigned int       __unused5;
+};
+#else
+struct kernel_stat64 {
+  unsigned long long st_dev;
+  unsigned char      __pad0[4];
+  unsigned           __st_ino;
+  unsigned           st_mode;
+  unsigned           st_nlink;
+  unsigned           st_uid;
+  unsigned           st_gid;
+  unsigned long long st_rdev;
+  unsigned char      __pad3[4];
+  long long          st_size;
+  unsigned           st_blksize;
+  unsigned long long st_blocks;
+  unsigned           st_atime_;
+  unsigned           st_atime_nsec_;
+  unsigned           st_mtime_;
+  unsigned           st_mtime_nsec_;
+  unsigned           st_ctime_;
+  unsigned           st_ctime_nsec_;
+  unsigned long long st_ino;
+};
+#endif
+
+/* include/asm-{arm,aarch64,i386,mips,x86_64,ppc,s390}/stat.h                */
+#if defined(__i386__) || defined(__ARM_ARCH_3__) || defined(__ARM_EABI__)
+struct kernel_stat {
+  /* The kernel headers suggest that st_dev and st_rdev should be 32bit
+   * quantities encoding 12bit major and 20bit minor numbers in an interleaved
+   * format. In reality, we do not see useful data in the top bits. So,
+   * we'll leave the padding in here, until we find a better solution.
+   */
+  unsigned short     st_dev;
+  short              pad1;
+  unsigned           st_ino;
+  unsigned short     st_mode;
+  unsigned short     st_nlink;
+  unsigned short     st_uid;
+  unsigned short     st_gid;
+  unsigned short     st_rdev;
+  short              pad2;
+  unsigned           st_size;
+  unsigned           st_blksize;
+  unsigned           st_blocks;
+  unsigned           st_atime_;
+  unsigned           st_atime_nsec_;
+  unsigned           st_mtime_;
+  unsigned           st_mtime_nsec_;
+  unsigned           st_ctime_;
+  unsigned           st_ctime_nsec_;
+  unsigned           __unused4;
+  unsigned           __unused5;
+};
+#elif defined(__x86_64__)
+struct kernel_stat {
+  uint64_t           st_dev;
+  uint64_t           st_ino;
+  uint64_t           st_nlink;
+  unsigned           st_mode;
+  unsigned           st_uid;
+  unsigned           st_gid;
+  unsigned           __pad0;
+  uint64_t           st_rdev;
+  int64_t            st_size;
+  int64_t            st_blksize;
+  int64_t            st_blocks;
+  uint64_t           st_atime_;
+  uint64_t           st_atime_nsec_;
+  uint64_t           st_mtime_;
+  uint64_t           st_mtime_nsec_;
+  uint64_t           st_ctime_;
+  uint64_t           st_ctime_nsec_;
+  int64_t            __unused4[3];
+};
+#elif defined(__PPC__)
+struct kernel_stat {
+  unsigned           st_dev;
+  unsigned long      st_ino;      // ino_t
+  unsigned long      st_mode;     // mode_t
+  unsigned short     st_nlink;    // nlink_t
+  unsigned           st_uid;      // uid_t
+  unsigned           st_gid;      // gid_t
+  unsigned           st_rdev;
+  long               st_size;     // off_t
+  unsigned long      st_blksize;
+  unsigned long      st_blocks;
+  unsigned long      st_atime_;
+  unsigned long      st_atime_nsec_;
+  unsigned long      st_mtime_;
+  unsigned long      st_mtime_nsec_;
+  unsigned long      st_ctime_;
+  unsigned long      st_ctime_nsec_;
+  unsigned long      __unused4;
+  unsigned long      __unused5;
+};
+#elif (defined(__mips__) && _MIPS_SIM != _MIPS_SIM_ABI64)
+struct kernel_stat {
+  unsigned           st_dev;
+  int                st_pad1[3];
+  unsigned           st_ino;
+  unsigned           st_mode;
+  unsigned           st_nlink;
+  unsigned           st_uid;
+  unsigned           st_gid;
+  unsigned           st_rdev;
+  int                st_pad2[2];
+  long               st_size;
+  int                st_pad3;
+  long               st_atime_;
+  long               st_atime_nsec_;
+  long               st_mtime_;
+  long               st_mtime_nsec_;
+  long               st_ctime_;
+  long               st_ctime_nsec_;
+  int                st_blksize;
+  int                st_blocks;
+  int                st_pad4[14];
+};
+#elif defined(__aarch64__)
+struct kernel_stat {
+  unsigned long      st_dev;
+  unsigned long      st_ino;
+  unsigned int       st_mode;
+  unsigned int       st_nlink;
+  unsigned int       st_uid;
+  unsigned int       st_gid;
+  unsigned long      st_rdev;
+  unsigned long      __pad1;
+  long               st_size;
+  int                st_blksize;
+  int                __pad2;
+  long               st_blocks;
+  long               st_atime_;
+  unsigned long      st_atime_nsec_;
+  long               st_mtime_;
+  unsigned long      st_mtime_nsec_;
+  long               st_ctime_;
+  unsigned long      st_ctime_nsec_;
+  unsigned int       __unused4;
+  unsigned int       __unused5;
+};
+#elif defined(__s390x__)
+struct kernel_stat {
+  unsigned long      st_dev;
+  unsigned long      st_ino;
+  unsigned long      st_nlink;
+  unsigned int       st_mode;
+  unsigned int       st_uid;
+  unsigned int       st_gid;
+  unsigned int       __pad1;
+  unsigned long      st_rdev;
+  unsigned long      st_size;
+  unsigned long      st_atime_;
+  unsigned long      st_atime_nsec_;
+  unsigned long      st_mtime_;
+  unsigned long      st_mtime_nsec_;
+  unsigned long      st_ctime_;
+  unsigned long      st_ctime_nsec_;
+  unsigned long      st_blksize;
+  long               st_blocks;
+  unsigned long      __unused[3];
+};
+#elif defined(__s390__)
+struct kernel_stat {
+  unsigned short     st_dev;
+  unsigned short     __pad1;
+  unsigned long      st_ino;
+  unsigned short     st_mode;
+  unsigned short     st_nlink;
+  unsigned short     st_uid;
+  unsigned short     st_gid;
+  unsigned short     st_rdev;
+  unsigned short     __pad2;
+  unsigned long      st_size;
+  unsigned long      st_blksize;
+  unsigned long      st_blocks;
+  unsigned long      st_atime_;
+  unsigned long      st_atime_nsec_;
+  unsigned long      st_mtime_;
+  unsigned long      st_mtime_nsec_;
+  unsigned long      st_ctime_;
+  unsigned long      st_ctime_nsec_;
+  unsigned long      __unused4;
+  unsigned long      __unused5;
+};
+#elif defined(__e2k__)
+struct kernel_stat {
+  unsigned long      st_dev;
+  unsigned long      st_ino;
+  unsigned int       st_mode;
+  unsigned long      st_nlink;
+  unsigned int       st_uid;
+  unsigned int       st_gid;
+  unsigned long      st_rdev;
+  unsigned long      st_size;
+  unsigned long      st_blksize;
+  unsigned long      st_blocks;
+  unsigned long      st_atime_;
+  unsigned long      st_atime_nsec_;
+  unsigned long      st_mtime_;
+  unsigned long      st_mtime_nsec_;
+  unsigned long      st_ctime_;
+  unsigned long      st_ctime_nsec_;
+};
+#endif
+
+/* include/asm-{arm,aarch64,i386,mips,x86_64,ppc,s390}/statfs.h              */
+#ifdef __mips__
+#if _MIPS_SIM != _MIPS_SIM_ABI64
+struct kernel_statfs64 {
+  unsigned long      f_type;
+  unsigned long      f_bsize;
+  unsigned long      f_frsize;
+  unsigned long      __pad;
+  unsigned long long f_blocks;
+  unsigned long long f_bfree;
+  unsigned long long f_files;
+  unsigned long long f_ffree;
+  unsigned long long f_bavail;
+  struct { int val[2]; } f_fsid;
+  unsigned long      f_namelen;
+  unsigned long      f_spare[6];
+};
+#endif
+#elif defined(__s390__)
+/* See also arch/s390/include/asm/compat.h                                   */
+struct kernel_statfs64 {
+  unsigned int       f_type;
+  unsigned int       f_bsize;
+  unsigned long long f_blocks;
+  unsigned long long f_bfree;
+  unsigned long long f_bavail;
+  unsigned long long f_files;
+  unsigned long long f_ffree;
+  struct { int val[2]; } f_fsid;
+  unsigned int       f_namelen;
+  unsigned int       f_frsize;
+  unsigned int       f_flags;
+  unsigned int       f_spare[4];
+};
+#elif !defined(__x86_64__)
+struct kernel_statfs64 {
+  unsigned long      f_type;
+  unsigned long      f_bsize;
+  unsigned long long f_blocks;
+  unsigned long long f_bfree;
+  unsigned long long f_bavail;
+  unsigned long long f_files;
+  unsigned long long f_ffree;
+  struct { int val[2]; } f_fsid;
+  unsigned long      f_namelen;
+  unsigned long      f_frsize;
+  unsigned long      f_spare[5];
+};
+#endif
+
+/* include/asm-{arm,i386,mips,x86_64,ppc,generic,s390}/statfs.h              */
+#ifdef __mips__
+struct kernel_statfs {
+  long               f_type;
+  long               f_bsize;
+  long               f_frsize;
+  long               f_blocks;
+  long               f_bfree;
+  long               f_files;
+  long               f_ffree;
+  long               f_bavail;
+  struct { int val[2]; } f_fsid;
+  long               f_namelen;
+  long               f_spare[6];
+};
+#elif defined(__x86_64__)
+struct kernel_statfs {
+  /* x86_64 actually defines all these fields as signed, whereas all other  */
+  /* platforms define them as unsigned. Leaving them at unsigned should not */
+  /* cause any problems. Make sure these are 64-bit even on x32.            */
+  uint64_t           f_type;
+  uint64_t           f_bsize;
+  uint64_t           f_blocks;
+  uint64_t           f_bfree;
+  uint64_t           f_bavail;
+  uint64_t           f_files;
+  uint64_t           f_ffree;
+  struct { int val[2]; } f_fsid;
+  uint64_t           f_namelen;
+  uint64_t           f_frsize;
+  uint64_t           f_spare[5];
+};
+#elif defined(__s390__)
+struct kernel_statfs {
+  unsigned int       f_type;
+  unsigned int       f_bsize;
+  unsigned long      f_blocks;
+  unsigned long      f_bfree;
+  unsigned long      f_bavail;
+  unsigned long      f_files;
+  unsigned long      f_ffree;
+  struct { int val[2]; } f_fsid;
+  unsigned int       f_namelen;
+  unsigned int       f_frsize;
+  unsigned int       f_flags;
+  unsigned int       f_spare[4];
+};
+#else
+struct kernel_statfs {
+  unsigned long      f_type;
+  unsigned long      f_bsize;
+  unsigned long      f_blocks;
+  unsigned long      f_bfree;
+  unsigned long      f_bavail;
+  unsigned long      f_files;
+  unsigned long      f_ffree;
+  struct { int val[2]; } f_fsid;
+  unsigned long      f_namelen;
+  unsigned long      f_frsize;
+  unsigned long      f_spare[5];
+};
+#endif
+
+
+/* Definitions missing from the standard header files                        */
+#ifndef O_DIRECTORY
+#if defined(__ARM_ARCH_3__) || defined(__ARM_EABI__) || defined(__aarch64__)
+#define O_DIRECTORY             0040000
+#else
+#define O_DIRECTORY             0200000
+#endif
+#endif
+#ifndef NT_PRXFPREG
+#define NT_PRXFPREG             0x46e62b7f
+#endif
+#ifndef PTRACE_GETFPXREGS
+#define PTRACE_GETFPXREGS       ((enum __ptrace_request)18)
+#endif
+#ifndef PR_GET_DUMPABLE
+#define PR_GET_DUMPABLE         3
+#endif
+#ifndef PR_SET_DUMPABLE
+#define PR_SET_DUMPABLE         4
+#endif
+#ifndef PR_GET_SECCOMP
+#define PR_GET_SECCOMP          21
+#endif
+#ifndef PR_SET_SECCOMP
+#define PR_SET_SECCOMP          22
+#endif
+#ifndef AT_FDCWD
+#define AT_FDCWD                (-100)
+#endif
+#ifndef AT_SYMLINK_NOFOLLOW
+#define AT_SYMLINK_NOFOLLOW     0x100
+#endif
+#ifndef AT_REMOVEDIR
+#define AT_REMOVEDIR            0x200
+#endif
+#ifndef MREMAP_FIXED
+#define MREMAP_FIXED            2
+#endif
+#ifndef SA_RESTORER
+#define SA_RESTORER             0x04000000
+#endif
+#ifndef CPUCLOCK_PROF
+#define CPUCLOCK_PROF           0
+#endif
+#ifndef CPUCLOCK_VIRT
+#define CPUCLOCK_VIRT           1
+#endif
+#ifndef CPUCLOCK_SCHED
+#define CPUCLOCK_SCHED          2
+#endif
+#ifndef CPUCLOCK_PERTHREAD_MASK
+#define CPUCLOCK_PERTHREAD_MASK 4
+#endif
+#ifndef MAKE_PROCESS_CPUCLOCK
+#define MAKE_PROCESS_CPUCLOCK(pid, clock)                                     \
+        ((int)(~(unsigned)(pid) << 3) | (int)(clock))
+#endif
+#ifndef MAKE_THREAD_CPUCLOCK
+#define MAKE_THREAD_CPUCLOCK(tid, clock)                                      \
+        ((int)(~(unsigned)(tid) << 3) |                                       \
+         (int)((clock) | CPUCLOCK_PERTHREAD_MASK))
+#endif
+
+#ifndef FUTEX_WAIT
+#define FUTEX_WAIT                0
+#endif
+#ifndef FUTEX_WAKE
+#define FUTEX_WAKE                1
+#endif
+#ifndef FUTEX_FD
+#define FUTEX_FD                  2
+#endif
+#ifndef FUTEX_REQUEUE
+#define FUTEX_REQUEUE             3
+#endif
+#ifndef FUTEX_CMP_REQUEUE
+#define FUTEX_CMP_REQUEUE         4
+#endif
+#ifndef FUTEX_WAKE_OP
+#define FUTEX_WAKE_OP             5
+#endif
+#ifndef FUTEX_LOCK_PI
+#define FUTEX_LOCK_PI             6
+#endif
+#ifndef FUTEX_UNLOCK_PI
+#define FUTEX_UNLOCK_PI           7
+#endif
+#ifndef FUTEX_TRYLOCK_PI
+#define FUTEX_TRYLOCK_PI          8
+#endif
+#ifndef FUTEX_PRIVATE_FLAG
+#define FUTEX_PRIVATE_FLAG        128
+#endif
+#ifndef FUTEX_CMD_MASK
+#define FUTEX_CMD_MASK            ~FUTEX_PRIVATE_FLAG
+#endif
+#ifndef FUTEX_WAIT_PRIVATE
+#define FUTEX_WAIT_PRIVATE        (FUTEX_WAIT | FUTEX_PRIVATE_FLAG)
+#endif
+#ifndef FUTEX_WAKE_PRIVATE
+#define FUTEX_WAKE_PRIVATE        (FUTEX_WAKE | FUTEX_PRIVATE_FLAG)
+#endif
+#ifndef FUTEX_REQUEUE_PRIVATE
+#define FUTEX_REQUEUE_PRIVATE     (FUTEX_REQUEUE | FUTEX_PRIVATE_FLAG)
+#endif
+#ifndef FUTEX_CMP_REQUEUE_PRIVATE
+#define FUTEX_CMP_REQUEUE_PRIVATE (FUTEX_CMP_REQUEUE | FUTEX_PRIVATE_FLAG)
+#endif
+#ifndef FUTEX_WAKE_OP_PRIVATE
+#define FUTEX_WAKE_OP_PRIVATE     (FUTEX_WAKE_OP | FUTEX_PRIVATE_FLAG)
+#endif
+#ifndef FUTEX_LOCK_PI_PRIVATE
+#define FUTEX_LOCK_PI_PRIVATE     (FUTEX_LOCK_PI | FUTEX_PRIVATE_FLAG)
+#endif
+#ifndef FUTEX_UNLOCK_PI_PRIVATE
+#define FUTEX_UNLOCK_PI_PRIVATE   (FUTEX_UNLOCK_PI | FUTEX_PRIVATE_FLAG)
+#endif
+#ifndef FUTEX_TRYLOCK_PI_PRIVATE
+#define FUTEX_TRYLOCK_PI_PRIVATE  (FUTEX_TRYLOCK_PI | FUTEX_PRIVATE_FLAG)
+#endif
+
+
+#if defined(__x86_64__)
+#ifndef ARCH_SET_GS
+#define ARCH_SET_GS             0x1001
+#endif
+#ifndef ARCH_GET_GS
+#define ARCH_GET_GS             0x1004
+#endif
+#endif
+
+#if defined(__i386__)
+#ifndef __NR_quotactl
+#define __NR_quotactl           131
+#endif
+#ifndef __NR_setresuid
+#define __NR_setresuid          164
+#define __NR_getresuid          165
+#define __NR_setresgid          170
+#define __NR_getresgid          171
+#endif
+#ifndef __NR_rt_sigaction
+#define __NR_rt_sigreturn       173
+#define __NR_rt_sigaction       174
+#define __NR_rt_sigprocmask     175
+#define __NR_rt_sigpending      176
+#define __NR_rt_sigsuspend      179
+#endif
+#ifndef __NR_pread64
+#define __NR_pread64            180
+#endif
+#ifndef __NR_pwrite64
+#define __NR_pwrite64           181
+#endif
+#ifndef __NR_ugetrlimit
+#define __NR_ugetrlimit         191
+#endif
+#ifndef __NR_stat64
+#define __NR_stat64             195
+#endif
+#ifndef __NR_fstat64
+#define __NR_fstat64            197
+#endif
+#ifndef __NR_setresuid32
+#define __NR_setresuid32        208
+#define __NR_getresuid32        209
+#define __NR_setresgid32        210
+#define __NR_getresgid32        211
+#endif
+#ifndef __NR_setfsuid32
+#define __NR_setfsuid32         215
+#define __NR_setfsgid32         216
+#endif
+#ifndef __NR_getdents64
+#define __NR_getdents64         220
+#endif
+#ifndef __NR_gettid
+#define __NR_gettid             224
+#endif
+#ifndef __NR_readahead
+#define __NR_readahead          225
+#endif
+#ifndef __NR_setxattr
+#define __NR_setxattr           226
+#endif
+#ifndef __NR_lsetxattr
+#define __NR_lsetxattr          227
+#endif
+#ifndef __NR_getxattr
+#define __NR_getxattr           229
+#endif
+#ifndef __NR_lgetxattr
+#define __NR_lgetxattr          230
+#endif
+#ifndef __NR_listxattr
+#define __NR_listxattr          232
+#endif
+#ifndef __NR_llistxattr
+#define __NR_llistxattr         233
+#endif
+#ifndef __NR_tkill
+#define __NR_tkill              238
+#endif
+#ifndef __NR_futex
+#define __NR_futex              240
+#endif
+#ifndef __NR_sched_setaffinity
+#define __NR_sched_setaffinity  241
+#define __NR_sched_getaffinity  242
+#endif
+#ifndef __NR_set_tid_address
+#define __NR_set_tid_address    258
+#endif
+#ifndef __NR_clock_gettime
+#define __NR_clock_gettime      265
+#endif
+#ifndef __NR_clock_getres
+#define __NR_clock_getres       266
+#endif
+#ifndef __NR_statfs64
+#define __NR_statfs64           268
+#endif
+#ifndef __NR_fstatfs64
+#define __NR_fstatfs64          269
+#endif
+#ifndef __NR_fadvise64_64
+#define __NR_fadvise64_64       272
+#endif
+#ifndef __NR_ioprio_set
+#define __NR_ioprio_set         289
+#endif
+#ifndef __NR_ioprio_get
+#define __NR_ioprio_get         290
+#endif
+#ifndef __NR_openat
+#define __NR_openat             295
+#endif
+#ifndef __NR_fstatat64
+#define __NR_fstatat64          300
+#endif
+#ifndef __NR_unlinkat
+#define __NR_unlinkat           301
+#endif
+#ifndef __NR_move_pages
+#define __NR_move_pages         317
+#endif
+#ifndef __NR_getcpu
+#define __NR_getcpu             318
+#endif
+#ifndef __NR_fallocate
+#define __NR_fallocate          324
+#endif
+#ifndef __NR_getrandom
+#define __NR_getrandom          355
+#endif
+/* End of i386 definitions                                                   */
+#elif defined(__ARM_ARCH_3__) || defined(__ARM_EABI__)
+#ifndef __NR_setresuid
+#define __NR_setresuid          (__NR_SYSCALL_BASE + 164)
+#define __NR_getresuid          (__NR_SYSCALL_BASE + 165)
+#define __NR_setresgid          (__NR_SYSCALL_BASE + 170)
+#define __NR_getresgid          (__NR_SYSCALL_BASE + 171)
+#endif
+#ifndef __NR_rt_sigaction
+#define __NR_rt_sigreturn       (__NR_SYSCALL_BASE + 173)
+#define __NR_rt_sigaction       (__NR_SYSCALL_BASE + 174)
+#define __NR_rt_sigprocmask     (__NR_SYSCALL_BASE + 175)
+#define __NR_rt_sigpending      (__NR_SYSCALL_BASE + 176)
+#define __NR_rt_sigsuspend      (__NR_SYSCALL_BASE + 179)
+#endif
+#ifndef __NR_pread64
+#define __NR_pread64            (__NR_SYSCALL_BASE + 180)
+#endif
+#ifndef __NR_pwrite64
+#define __NR_pwrite64           (__NR_SYSCALL_BASE + 181)
+#endif
+#ifndef __NR_ugetrlimit
+#define __NR_ugetrlimit         (__NR_SYSCALL_BASE + 191)
+#endif
+#ifndef __NR_stat64
+#define __NR_stat64             (__NR_SYSCALL_BASE + 195)
+#endif
+#ifndef __NR_fstat64
+#define __NR_fstat64            (__NR_SYSCALL_BASE + 197)
+#endif
+#ifndef __NR_setresuid32
+#define __NR_setresuid32        (__NR_SYSCALL_BASE + 208)
+#define __NR_getresuid32        (__NR_SYSCALL_BASE + 209)
+#define __NR_setresgid32        (__NR_SYSCALL_BASE + 210)
+#define __NR_getresgid32        (__NR_SYSCALL_BASE + 211)
+#endif
+#ifndef __NR_setfsuid32
+#define __NR_setfsuid32         (__NR_SYSCALL_BASE + 215)
+#define __NR_setfsgid32         (__NR_SYSCALL_BASE + 216)
+#endif
+#ifndef __NR_getdents64
+#define __NR_getdents64         (__NR_SYSCALL_BASE + 217)
+#endif
+#ifndef __NR_gettid
+#define __NR_gettid             (__NR_SYSCALL_BASE + 224)
+#endif
+#ifndef __NR_readahead
+#define __NR_readahead          (__NR_SYSCALL_BASE + 225)
+#endif
+#ifndef __NR_setxattr
+#define __NR_setxattr           (__NR_SYSCALL_BASE + 226)
+#endif
+#ifndef __NR_lsetxattr
+#define __NR_lsetxattr          (__NR_SYSCALL_BASE + 227)
+#endif
+#ifndef __NR_getxattr
+#define __NR_getxattr           (__NR_SYSCALL_BASE + 229)
+#endif
+#ifndef __NR_lgetxattr
+#define __NR_lgetxattr          (__NR_SYSCALL_BASE + 230)
+#endif
+#ifndef __NR_listxattr
+#define __NR_listxattr          (__NR_SYSCALL_BASE + 232)
+#endif
+#ifndef __NR_llistxattr
+#define __NR_llistxattr         (__NR_SYSCALL_BASE + 233)
+#endif
+#ifndef __NR_tkill
+#define __NR_tkill              (__NR_SYSCALL_BASE + 238)
+#endif
+#ifndef __NR_futex
+#define __NR_futex              (__NR_SYSCALL_BASE + 240)
+#endif
+#ifndef __NR_sched_setaffinity
+#define __NR_sched_setaffinity  (__NR_SYSCALL_BASE + 241)
+#define __NR_sched_getaffinity  (__NR_SYSCALL_BASE + 242)
+#endif
+#ifndef __NR_set_tid_address
+#define __NR_set_tid_address    (__NR_SYSCALL_BASE + 256)
+#endif
+#ifndef __NR_clock_gettime
+#define __NR_clock_gettime      (__NR_SYSCALL_BASE + 263)
+#endif
+#ifndef __NR_clock_getres
+#define __NR_clock_getres       (__NR_SYSCALL_BASE + 264)
+#endif
+#ifndef __NR_statfs64
+#define __NR_statfs64           (__NR_SYSCALL_BASE + 266)
+#endif
+#ifndef __NR_fstatfs64
+#define __NR_fstatfs64          (__NR_SYSCALL_BASE + 267)
+#endif
+#ifndef __NR_ioprio_set
+#define __NR_ioprio_set         (__NR_SYSCALL_BASE + 314)
+#endif
+#ifndef __NR_ioprio_get
+#define __NR_ioprio_get         (__NR_SYSCALL_BASE + 315)
+#endif
+#ifndef __NR_fstatat64
+#define __NR_fstatat64          (__NR_SYSCALL_BASE + 327)
+#endif
+#ifndef __NR_move_pages
+#define __NR_move_pages         (__NR_SYSCALL_BASE + 344)
+#endif
+#ifndef __NR_getcpu
+#define __NR_getcpu             (__NR_SYSCALL_BASE + 345)
+#endif
+#ifndef __NR_getrandom
+#define __NR_getrandom          (__NR_SYSCALL_BASE + 384)
+#endif
+/* End of ARM 3/EABI definitions                                             */
+#elif defined(__aarch64__)
+#ifndef __NR_setxattr
+#define __NR_setxattr             5
+#endif
+#ifndef __NR_lsetxattr
+#define __NR_lsetxattr            6
+#endif
+#ifndef __NR_getxattr
+#define __NR_getxattr             8
+#endif
+#ifndef __NR_lgetxattr
+#define __NR_lgetxattr            9
+#endif
+#ifndef __NR_listxattr
+#define __NR_listxattr           11
+#endif
+#ifndef __NR_llistxattr
+#define __NR_llistxattr          12
+#endif
+#ifndef __NR_ioprio_set
+#define __NR_ioprio_set          30
+#endif
+#ifndef __NR_ioprio_get
+#define __NR_ioprio_get          31
+#endif
+#ifndef __NR_unlinkat
+#define __NR_unlinkat            35
+#endif
+#ifndef __NR_fallocate
+#define __NR_fallocate           47
+#endif
+#ifndef __NR_openat
+#define __NR_openat              56
+#endif
+#ifndef __NR_quotactl
+#define __NR_quotactl            60
+#endif
+#ifndef __NR_getdents64
+#define __NR_getdents64          61
+#endif
+#ifndef __NR_getdents
+// when getdents is not available, getdents64 is used for both.
+#define __NR_getdents            __NR_getdents64
+#endif
+#ifndef __NR_pread64
+#define __NR_pread64             67
+#endif
+#ifndef __NR_pwrite64
+#define __NR_pwrite64            68
+#endif
+#ifndef __NR_ppoll
+#define __NR_ppoll               73
+#endif
+#ifndef __NR_readlinkat
+#define __NR_readlinkat          78
+#endif
+#ifndef __NR_newfstatat
+#define __NR_newfstatat          79
+#endif
+#ifndef __NR_set_tid_address
+#define __NR_set_tid_address     96
+#endif
+#ifndef __NR_futex
+#define __NR_futex               98
+#endif
+#ifndef __NR_clock_gettime
+#define __NR_clock_gettime      113
+#endif
+#ifndef __NR_clock_getres
+#define __NR_clock_getres       114
+#endif
+#ifndef __NR_sched_setaffinity
+#define __NR_sched_setaffinity  122
+#define __NR_sched_getaffinity  123
+#endif
+#ifndef __NR_tkill
+#define __NR_tkill              130
+#endif
+#ifndef __NR_setresuid
+#define __NR_setresuid          147
+#define __NR_getresuid          148
+#define __NR_setresgid          149
+#define __NR_getresgid          150
+#endif
+#ifndef __NR_gettid
+#define __NR_gettid             178
+#endif
+#ifndef __NR_readahead
+#define __NR_readahead          213
+#endif
+#ifndef __NR_fadvise64
+#define __NR_fadvise64          223
+#endif
+#ifndef __NR_move_pages
+#define __NR_move_pages         239
+#endif
+#ifndef __NR_getrandom
+#define __NR_getrandom          278
+#endif
+/* End of aarch64 definitions                                                */
+#elif defined(__x86_64__)
+#ifndef __NR_pread64
+#define __NR_pread64             17
+#endif
+#ifndef __NR_pwrite64
+#define __NR_pwrite64            18
+#endif
+#ifndef __NR_setresuid
+#define __NR_setresuid          117
+#define __NR_getresuid          118
+#define __NR_setresgid          119
+#define __NR_getresgid          120
+#endif
+#ifndef __NR_quotactl
+#define __NR_quotactl           179
+#endif
+#ifndef __NR_gettid
+#define __NR_gettid             186
+#endif
+#ifndef __NR_readahead
+#define __NR_readahead          187
+#endif
+#ifndef __NR_setxattr
+#define __NR_setxattr           188
+#endif
+#ifndef __NR_lsetxattr
+#define __NR_lsetxattr          189
+#endif
+#ifndef __NR_getxattr
+#define __NR_getxattr           191
+#endif
+#ifndef __NR_lgetxattr
+#define __NR_lgetxattr          192
+#endif
+#ifndef __NR_listxattr
+#define __NR_listxattr          194
+#endif
+#ifndef __NR_llistxattr
+#define __NR_llistxattr         195
+#endif
+#ifndef __NR_tkill
+#define __NR_tkill              200
+#endif
+#ifndef __NR_futex
+#define __NR_futex              202
+#endif
+#ifndef __NR_sched_setaffinity
+#define __NR_sched_setaffinity  203
+#define __NR_sched_getaffinity  204
+#endif
+#ifndef __NR_getdents64
+#define __NR_getdents64         217
+#endif
+#ifndef __NR_getdents
+// when getdents is not available, getdents64 is used for both.
+#define __NR_getdents           __NR_getdents64
+#endif
+#ifndef __NR_set_tid_address
+#define __NR_set_tid_address    218
+#endif
+#ifndef __NR_fadvise64
+#define __NR_fadvise64          221
+#endif
+#ifndef __NR_clock_gettime
+#define __NR_clock_gettime      228
+#endif
+#ifndef __NR_clock_getres
+#define __NR_clock_getres       229
+#endif
+#ifndef __NR_ioprio_set
+#define __NR_ioprio_set         251
+#endif
+#ifndef __NR_ioprio_get
+#define __NR_ioprio_get         252
+#endif
+#ifndef __NR_openat
+#define __NR_openat             257
+#endif
+#ifndef __NR_newfstatat
+#define __NR_newfstatat         262
+#endif
+#ifndef __NR_unlinkat
+#define __NR_unlinkat           263
+#endif
+#ifndef __NR_move_pages
+#define __NR_move_pages         279
+#endif
+#ifndef __NR_fallocate
+#define __NR_fallocate          285
+#endif
+#ifndef __NR_getrandom
+#define __NR_getrandom          318
+#endif
+/* End of x86-64 definitions                                                 */
+#elif defined(__mips__)
+#if _MIPS_SIM == _MIPS_SIM_ABI32
+#ifndef __NR_setresuid
+#define __NR_setresuid          (__NR_Linux + 185)
+#define __NR_getresuid          (__NR_Linux + 186)
+#define __NR_setresgid          (__NR_Linux + 190)
+#define __NR_getresgid          (__NR_Linux + 191)
+#endif
+#ifndef __NR_rt_sigaction
+#define __NR_rt_sigreturn       (__NR_Linux + 193)
+#define __NR_rt_sigaction       (__NR_Linux + 194)
+#define __NR_rt_sigprocmask     (__NR_Linux + 195)
+#define __NR_rt_sigpending      (__NR_Linux + 196)
+#define __NR_rt_sigsuspend      (__NR_Linux + 199)
+#endif
+#ifndef __NR_pread64
+#define __NR_pread64            (__NR_Linux + 200)
+#endif
+#ifndef __NR_pwrite64
+#define __NR_pwrite64           (__NR_Linux + 201)
+#endif
+#ifndef __NR_stat64
+#define __NR_stat64             (__NR_Linux + 213)
+#endif
+#ifndef __NR_fstat64
+#define __NR_fstat64            (__NR_Linux + 215)
+#endif
+#ifndef __NR_getdents64
+#define __NR_getdents64         (__NR_Linux + 219)
+#endif
+#ifndef __NR_gettid
+#define __NR_gettid             (__NR_Linux + 222)
+#endif
+#ifndef __NR_readahead
+#define __NR_readahead          (__NR_Linux + 223)
+#endif
+#ifndef __NR_setxattr
+#define __NR_setxattr           (__NR_Linux + 224)
+#endif
+#ifndef __NR_lsetxattr
+#define __NR_lsetxattr          (__NR_Linux + 225)
+#endif
+#ifndef __NR_getxattr
+#define __NR_getxattr           (__NR_Linux + 227)
+#endif
+#ifndef __NR_lgetxattr
+#define __NR_lgetxattr          (__NR_Linux + 228)
+#endif
+#ifndef __NR_listxattr
+#define __NR_listxattr          (__NR_Linux + 230)
+#endif
+#ifndef __NR_llistxattr
+#define __NR_llistxattr         (__NR_Linux + 231)
+#endif
+#ifndef __NR_tkill
+#define __NR_tkill              (__NR_Linux + 236)
+#endif
+#ifndef __NR_futex
+#define __NR_futex              (__NR_Linux + 238)
+#endif
+#ifndef __NR_sched_setaffinity
+#define __NR_sched_setaffinity  (__NR_Linux + 239)
+#define __NR_sched_getaffinity  (__NR_Linux + 240)
+#endif
+#ifndef __NR_set_tid_address
+#define __NR_set_tid_address    (__NR_Linux + 252)
+#endif
+#ifndef __NR_statfs64
+#define __NR_statfs64           (__NR_Linux + 255)
+#endif
+#ifndef __NR_fstatfs64
+#define __NR_fstatfs64          (__NR_Linux + 256)
+#endif
+#ifndef __NR_clock_gettime
+#define __NR_clock_gettime      (__NR_Linux + 263)
+#endif
+#ifndef __NR_clock_getres
+#define __NR_clock_getres       (__NR_Linux + 264)
+#endif
+#ifndef __NR_openat
+#define __NR_openat             (__NR_Linux + 288)
+#endif
+#ifndef __NR_fstatat
+#define __NR_fstatat            (__NR_Linux + 293)
+#endif
+#ifndef __NR_unlinkat
+#define __NR_unlinkat           (__NR_Linux + 294)
+#endif
+#ifndef __NR_move_pages
+#define __NR_move_pages         (__NR_Linux + 308)
+#endif
+#ifndef __NR_getcpu
+#define __NR_getcpu             (__NR_Linux + 312)
+#endif
+#ifndef __NR_ioprio_set
+#define __NR_ioprio_set         (__NR_Linux + 314)
+#endif
+#ifndef __NR_ioprio_get
+#define __NR_ioprio_get         (__NR_Linux + 315)
+#endif
+#ifndef __NR_getrandom
+#define __NR_getrandom          (__NR_Linux + 353)
+#endif
+/* End of MIPS (old 32bit API) definitions */
+#elif  _MIPS_SIM == _MIPS_SIM_ABI64
+#ifndef __NR_pread64
+#define __NR_pread64            (__NR_Linux +  16)
+#endif
+#ifndef __NR_pwrite64
+#define __NR_pwrite64           (__NR_Linux +  17)
+#endif
+#ifndef __NR_setresuid
+#define __NR_setresuid          (__NR_Linux + 115)
+#define __NR_getresuid          (__NR_Linux + 116)
+#define __NR_setresgid          (__NR_Linux + 117)
+#define __NR_getresgid          (__NR_Linux + 118)
+#endif
+#ifndef __NR_gettid
+#define __NR_gettid             (__NR_Linux + 178)
+#endif
+#ifndef __NR_readahead
+#define __NR_readahead          (__NR_Linux + 179)
+#endif
+#ifndef __NR_setxattr
+#define __NR_setxattr           (__NR_Linux + 180)
+#endif
+#ifndef __NR_lsetxattr
+#define __NR_lsetxattr          (__NR_Linux + 181)
+#endif
+#ifndef __NR_getxattr
+#define __NR_getxattr           (__NR_Linux + 183)
+#endif
+#ifndef __NR_lgetxattr
+#define __NR_lgetxattr          (__NR_Linux + 184)
+#endif
+#ifndef __NR_listxattr
+#define __NR_listxattr          (__NR_Linux + 186)
+#endif
+#ifndef __NR_llistxattr
+#define __NR_llistxattr         (__NR_Linux + 187)
+#endif
+#ifndef __NR_tkill
+#define __NR_tkill              (__NR_Linux + 192)
+#endif
+#ifndef __NR_futex
+#define __NR_futex              (__NR_Linux + 194)
+#endif
+#ifndef __NR_sched_setaffinity
+#define __NR_sched_setaffinity  (__NR_Linux + 195)
+#define __NR_sched_getaffinity  (__NR_Linux + 196)
+#endif
+#ifndef __NR_set_tid_address
+#define __NR_set_tid_address    (__NR_Linux + 212)
+#endif
+#ifndef __NR_clock_gettime
+#define __NR_clock_gettime      (__NR_Linux + 222)
+#endif
+#ifndef __NR_clock_getres
+#define __NR_clock_getres       (__NR_Linux + 223)
+#endif
+#ifndef __NR_openat
+#define __NR_openat             (__NR_Linux + 247)
+#endif
+#ifndef __NR_fstatat
+#define __NR_fstatat            (__NR_Linux + 252)
+#endif
+#ifndef __NR_unlinkat
+#define __NR_unlinkat           (__NR_Linux + 253)
+#endif
+#ifndef __NR_move_pages
+#define __NR_move_pages         (__NR_Linux + 267)
+#endif
+#ifndef __NR_getcpu
+#define __NR_getcpu             (__NR_Linux + 271)
+#endif
+#ifndef __NR_ioprio_set
+#define __NR_ioprio_set         (__NR_Linux + 273)
+#endif
+#ifndef __NR_ioprio_get
+#define __NR_ioprio_get         (__NR_Linux + 274)
+#endif
+#ifndef __NR_getrandom
+#define __NR_getrandom          (__NR_Linux + 313)
+#endif
+/* End of MIPS (64bit API) definitions */
+#else
+#ifndef __NR_setresuid
+#define __NR_setresuid          (__NR_Linux + 115)
+#define __NR_getresuid          (__NR_Linux + 116)
+#define __NR_setresgid          (__NR_Linux + 117)
+#define __NR_getresgid          (__NR_Linux + 118)
+#endif
+#ifndef __NR_gettid
+#define __NR_gettid             (__NR_Linux + 178)
+#endif
+#ifndef __NR_readahead
+#define __NR_readahead          (__NR_Linux + 179)
+#endif
+#ifndef __NR_setxattr
+#define __NR_setxattr           (__NR_Linux + 180)
+#endif
+#ifndef __NR_lsetxattr
+#define __NR_lsetxattr          (__NR_Linux + 181)
+#endif
+#ifndef __NR_getxattr
+#define __NR_getxattr           (__NR_Linux + 183)
+#endif
+#ifndef __NR_lgetxattr
+#define __NR_lgetxattr          (__NR_Linux + 184)
+#endif
+#ifndef __NR_listxattr
+#define __NR_listxattr          (__NR_Linux + 186)
+#endif
+#ifndef __NR_llistxattr
+#define __NR_llistxattr         (__NR_Linux + 187)
+#endif
+#ifndef __NR_tkill
+#define __NR_tkill              (__NR_Linux + 192)
+#endif
+#ifndef __NR_futex
+#define __NR_futex              (__NR_Linux + 194)
+#endif
+#ifndef __NR_sched_setaffinity
+#define __NR_sched_setaffinity  (__NR_Linux + 195)
+#define __NR_sched_getaffinity  (__NR_Linux + 196)
+#endif
+#ifndef __NR_set_tid_address
+#define __NR_set_tid_address    (__NR_Linux + 213)
+#endif
+#ifndef __NR_statfs64
+#define __NR_statfs64           (__NR_Linux + 217)
+#endif
+#ifndef __NR_fstatfs64
+#define __NR_fstatfs64          (__NR_Linux + 218)
+#endif
+#ifndef __NR_clock_gettime
+#define __NR_clock_gettime      (__NR_Linux + 226)
+#endif
+#ifndef __NR_clock_getres
+#define __NR_clock_getres       (__NR_Linux + 227)
+#endif
+#ifndef __NR_openat
+#define __NR_openat             (__NR_Linux + 251)
+#endif
+#ifndef __NR_fstatat
+#define __NR_fstatat            (__NR_Linux + 256)
+#endif
+#ifndef __NR_unlinkat
+#define __NR_unlinkat           (__NR_Linux + 257)
+#endif
+#ifndef __NR_move_pages
+#define __NR_move_pages         (__NR_Linux + 271)
+#endif
+#ifndef __NR_getcpu
+#define __NR_getcpu             (__NR_Linux + 275)
+#endif
+#ifndef __NR_ioprio_set
+#define __NR_ioprio_set         (__NR_Linux + 277)
+#endif
+#ifndef __NR_ioprio_get
+#define __NR_ioprio_get         (__NR_Linux + 278)
+#endif
+/* End of MIPS (new 32bit API) definitions                                   */
+#endif
+/* End of MIPS definitions                                                   */
+#elif defined(__PPC__)
+#ifndef __NR_setfsuid
+#define __NR_setfsuid           138
+#define __NR_setfsgid           139
+#endif
+#ifndef __NR_setresuid
+#define __NR_setresuid          164
+#define __NR_getresuid          165
+#define __NR_setresgid          169
+#define __NR_getresgid          170
+#endif
+#ifndef __NR_rt_sigaction
+#define __NR_rt_sigreturn       172
+#define __NR_rt_sigaction       173
+#define __NR_rt_sigprocmask     174
+#define __NR_rt_sigpending      175
+#define __NR_rt_sigsuspend      178
+#endif
+#ifndef __NR_pread64
+#define __NR_pread64            179
+#endif
+#ifndef __NR_pwrite64
+#define __NR_pwrite64           180
+#endif
+#ifndef __NR_ugetrlimit
+#define __NR_ugetrlimit         190
+#endif
+#ifndef __NR_readahead
+#define __NR_readahead          191
+#endif
+#ifndef __NR_stat64
+#define __NR_stat64             195
+#endif
+#ifndef __NR_fstat64
+#define __NR_fstat64            197
+#endif
+#ifndef __NR_getdents64
+#define __NR_getdents64         202
+#endif
+#ifndef __NR_gettid
+#define __NR_gettid             207
+#endif
+#ifndef __NR_tkill
+#define __NR_tkill              208
+#endif
+#ifndef __NR_setxattr
+#define __NR_setxattr           209
+#endif
+#ifndef __NR_lsetxattr
+#define __NR_lsetxattr          210
+#endif
+#ifndef __NR_getxattr
+#define __NR_getxattr           212
+#endif
+#ifndef __NR_lgetxattr
+#define __NR_lgetxattr          213
+#endif
+#ifndef __NR_listxattr
+#define __NR_listxattr          215
+#endif
+#ifndef __NR_llistxattr
+#define __NR_llistxattr         216
+#endif
+#ifndef __NR_futex
+#define __NR_futex              221
+#endif
+#ifndef __NR_sched_setaffinity
+#define __NR_sched_setaffinity  222
+#define __NR_sched_getaffinity  223
+#endif
+#ifndef __NR_set_tid_address
+#define __NR_set_tid_address    232
+#endif
+#ifndef __NR_clock_gettime
+#define __NR_clock_gettime      246
+#endif
+#ifndef __NR_clock_getres
+#define __NR_clock_getres       247
+#endif
+#ifndef __NR_statfs64
+#define __NR_statfs64           252
+#endif
+#ifndef __NR_fstatfs64
+#define __NR_fstatfs64          253
+#endif
+#ifndef __NR_fadvise64_64
+#define __NR_fadvise64_64       254
+#endif
+#ifndef __NR_ioprio_set
+#define __NR_ioprio_set         273
+#endif
+#ifndef __NR_ioprio_get
+#define __NR_ioprio_get         274
+#endif
+#ifndef __NR_openat
+#define __NR_openat             286
+#endif
+#ifndef __NR_fstatat64
+#define __NR_fstatat64          291
+#endif
+#ifndef __NR_unlinkat
+#define __NR_unlinkat           292
+#endif
+#ifndef __NR_move_pages
+#define __NR_move_pages         301
+#endif
+#ifndef __NR_getcpu
+#define __NR_getcpu             302
+#endif
+/* End of powerpc defininitions                                              */
+#elif defined(__s390__)
+#ifndef __NR_quotactl
+#define __NR_quotactl           131
+#endif
+#ifndef __NR_rt_sigreturn
+#define __NR_rt_sigreturn       173
+#endif
+#ifndef __NR_rt_sigaction
+#define __NR_rt_sigaction       174
+#endif
+#ifndef __NR_rt_sigprocmask
+#define __NR_rt_sigprocmask     175
+#endif
+#ifndef __NR_rt_sigpending
+#define __NR_rt_sigpending      176
+#endif
+#ifndef __NR_rt_sigsuspend
+#define __NR_rt_sigsuspend      179
+#endif
+#ifndef __NR_pread64
+#define __NR_pread64            180
+#endif
+#ifndef __NR_pwrite64
+#define __NR_pwrite64           181
+#endif
+#ifndef __NR_getdents64
+#define __NR_getdents64         220
+#endif
+#ifndef __NR_readahead
+#define __NR_readahead          222
+#endif
+#ifndef __NR_setxattr
+#define __NR_setxattr           224
+#endif
+#ifndef __NR_lsetxattr
+#define __NR_lsetxattr          225
+#endif
+#ifndef __NR_getxattr
+#define __NR_getxattr           227
+#endif
+#ifndef __NR_lgetxattr
+#define __NR_lgetxattr          228
+#endif
+#ifndef __NR_listxattr
+#define __NR_listxattr          230
+#endif
+#ifndef __NR_llistxattr
+#define __NR_llistxattr         231
+#endif
+#ifndef __NR_gettid
+#define __NR_gettid             236
+#endif
+#ifndef __NR_tkill
+#define __NR_tkill              237
+#endif
+#ifndef __NR_futex
+#define __NR_futex              238
+#endif
+#ifndef __NR_sched_setaffinity
+#define __NR_sched_setaffinity  239
+#endif
+#ifndef __NR_sched_getaffinity
+#define __NR_sched_getaffinity  240
+#endif
+#ifndef __NR_set_tid_address
+#define __NR_set_tid_address    252
+#endif
+#ifndef __NR_clock_gettime
+#define __NR_clock_gettime      260
+#endif
+#ifndef __NR_clock_getres
+#define __NR_clock_getres       261
+#endif
+#ifndef __NR_statfs64
+#define __NR_statfs64           265
+#endif
+#ifndef __NR_fstatfs64
+#define __NR_fstatfs64          266
+#endif
+#ifndef __NR_ioprio_set
+#define __NR_ioprio_set         282
+#endif
+#ifndef __NR_ioprio_get
+#define __NR_ioprio_get         283
+#endif
+#ifndef __NR_openat
+#define __NR_openat             288
+#endif
+#ifndef __NR_unlinkat
+#define __NR_unlinkat           294
+#endif
+#ifndef __NR_move_pages
+#define __NR_move_pages         310
+#endif
+#ifndef __NR_getcpu
+#define __NR_getcpu             311
+#endif
+#ifndef __NR_fallocate
+#define __NR_fallocate          314
+#endif
+/* Some syscalls are named/numbered differently between s390 and s390x. */
+#ifdef __s390x__
+# ifndef __NR_getrlimit
+# define __NR_getrlimit          191
+# endif
+# ifndef __NR_setresuid
+# define __NR_setresuid          208
+# endif
+# ifndef __NR_getresuid
+# define __NR_getresuid          209
+# endif
+# ifndef __NR_setresgid
+# define __NR_setresgid          210
+# endif
+# ifndef __NR_getresgid
+# define __NR_getresgid          211
+# endif
+# ifndef __NR_setfsuid
+# define __NR_setfsuid           215
+# endif
+# ifndef __NR_setfsgid
+# define __NR_setfsgid           216
+# endif
+# ifndef __NR_fadvise64
+# define __NR_fadvise64          253
+# endif
+# ifndef __NR_newfstatat
+# define __NR_newfstatat         293
+# endif
+#else /* __s390x__ */
+# ifndef __NR_getrlimit
+# define __NR_getrlimit          76
+# endif
+# ifndef __NR_setfsuid
+# define __NR_setfsuid           138
+# endif
+# ifndef __NR_setfsgid
+# define __NR_setfsgid           139
+# endif
+# ifndef __NR_setresuid
+# define __NR_setresuid          164
+# endif
+# ifndef __NR_getresuid
+# define __NR_getresuid          165
+# endif
+# ifndef __NR_setresgid
+# define __NR_setresgid          170
+# endif
+# ifndef __NR_getresgid
+# define __NR_getresgid          171
+# endif
+# ifndef __NR_ugetrlimit
+# define __NR_ugetrlimit         191
+# endif
+# ifndef __NR_mmap2
+# define __NR_mmap2              192
+# endif
+# ifndef __NR_setresuid32
+# define __NR_setresuid32        208
+# endif
+# ifndef __NR_getresuid32
+# define __NR_getresuid32        209
+# endif
+# ifndef __NR_setresgid32
+# define __NR_setresgid32        210
+# endif
+# ifndef __NR_getresgid32
+# define __NR_getresgid32        211
+# endif
+# ifndef __NR_setfsuid32
+# define __NR_setfsuid32         215
+# endif
+# ifndef __NR_setfsgid32
+# define __NR_setfsgid32         216
+# endif
+# ifndef __NR_fadvise64_64
+# define __NR_fadvise64_64       264
+# endif
+# ifndef __NR_fstatat64
+# define __NR_fstatat64          293
+# endif
+#endif /* __s390__ */
+/* End of s390/s390x definitions                                             */
+#endif
+
+
+/* After forking, we must make sure to only call system calls.               */
+#if defined(__BOUNDED_POINTERS__)
+  #error "Need to port invocations of syscalls for bounded ptrs"
+#else
+  /* The core dumper and the thread lister get executed after threads
+   * have been suspended. As a consequence, we cannot call any functions
+   * that acquire locks. Unfortunately, libc wraps most system calls
+   * (e.g. in order to implement pthread_atfork, and to make calls
+   * cancellable), which means we cannot call these functions. Instead,
+   * we have to call syscall() directly.
+   */
+  #undef LSS_ERRNO
+  #ifdef SYS_ERRNO
+    /* Allow the including file to override the location of errno. This can
+     * be useful when using clone() with the CLONE_VM option.
+     */
+    #define LSS_ERRNO SYS_ERRNO
+  #else
+    #define LSS_ERRNO errno
+  #endif
+
+  #undef LSS_INLINE
+  #ifdef SYS_INLINE
+    #define LSS_INLINE SYS_INLINE
+  #else
+    #define LSS_INLINE static inline
+  #endif
+
+  /* Allow the including file to override the prefix used for all new
+   * system calls. By default, it will be set to "sys_".
+   */
+  #undef LSS_NAME
+  #ifndef SYS_PREFIX
+    #define LSS_NAME(name) sys_##name
+  #elif defined(SYS_PREFIX) && SYS_PREFIX < 0
+    #define LSS_NAME(name) name
+  #elif defined(SYS_PREFIX) && SYS_PREFIX == 0
+    #define LSS_NAME(name) sys0_##name
+  #elif defined(SYS_PREFIX) && SYS_PREFIX == 1
+    #define LSS_NAME(name) sys1_##name
+  #elif defined(SYS_PREFIX) && SYS_PREFIX == 2
+    #define LSS_NAME(name) sys2_##name
+  #elif defined(SYS_PREFIX) && SYS_PREFIX == 3
+    #define LSS_NAME(name) sys3_##name
+  #elif defined(SYS_PREFIX) && SYS_PREFIX == 4
+    #define LSS_NAME(name) sys4_##name
+  #elif defined(SYS_PREFIX) && SYS_PREFIX == 5
+    #define LSS_NAME(name) sys5_##name
+  #elif defined(SYS_PREFIX) && SYS_PREFIX == 6
+    #define LSS_NAME(name) sys6_##name
+  #elif defined(SYS_PREFIX) && SYS_PREFIX == 7
+    #define LSS_NAME(name) sys7_##name
+  #elif defined(SYS_PREFIX) && SYS_PREFIX == 8
+    #define LSS_NAME(name) sys8_##name
+  #elif defined(SYS_PREFIX) && SYS_PREFIX == 9
+    #define LSS_NAME(name) sys9_##name
+  #endif
+
+  #undef  LSS_RETURN
+  #if defined(__i386__) || defined(__x86_64__) || defined(__ARM_ARCH_3__) \
+       || defined(__ARM_EABI__) || defined(__aarch64__) || defined(__s390__) \
+       || defined(__e2k__)
+  /* Failing system calls return a negative result in the range of
+   * -1..-4095. These are "errno" values with the sign inverted.
+   */
+  #define LSS_RETURN(type, res)                                               \
+    do {                                                                      \
+      if ((unsigned long)(res) >= (unsigned long)(-4095)) {                   \
+        LSS_ERRNO = -(res);                                                   \
+        res = -1;                                                             \
+      }                                                                       \
+      return (type) (res);                                                    \
+    } while (0)
+  #elif defined(__mips__)
+  /* On MIPS, failing system calls return -1, and set errno in a
+   * separate CPU register.
+   */
+  #define LSS_RETURN(type, res, err)                                          \
+    do {                                                                      \
+      if (err) {                                                              \
+        unsigned long __errnovalue = (res);                                   \
+        LSS_ERRNO = __errnovalue;                                             \
+        res = -1;                                                             \
+      }                                                                       \
+      return (type) (res);                                                    \
+    } while (0)
+  #elif defined(__PPC__)
+  /* On PPC, failing system calls return -1, and set errno in a
+   * separate CPU register. See linux/unistd.h.
+   */
+  #define LSS_RETURN(type, res, err)                                          \
+   do {                                                                       \
+     if (err & 0x10000000 ) {                                                 \
+       LSS_ERRNO = (res);                                                     \
+       res = -1;                                                              \
+     }                                                                        \
+     return (type) (res);                                                     \
+   } while (0)
+  #endif
+  #if defined(__i386__)
+    /* In PIC mode (e.g. when building shared libraries), gcc for i386
+     * reserves ebx. Unfortunately, most distribution ship with implementations
+     * of _syscallX() which clobber ebx.
+     * Also, most definitions of _syscallX() neglect to mark "memory" as being
+     * clobbered. This causes problems with compilers, that do a better job
+     * at optimizing across __asm__ calls.
+     * So, we just have to redefine all of the _syscallX() macros.
+     */
+    #undef LSS_ENTRYPOINT
+    #ifdef SYS_SYSCALL_ENTRYPOINT
+    static inline void (**LSS_NAME(get_syscall_entrypoint)(void))(void) {
+      void (**entrypoint)(void);
+      asm volatile(".bss\n"
+                   ".align 8\n"
+                   ".globl " SYS_SYSCALL_ENTRYPOINT "\n"
+                   ".common " SYS_SYSCALL_ENTRYPOINT ",8,8\n"
+                   ".previous\n"
+                   /* This logically does 'lea "SYS_SYSCALL_ENTRYPOINT", %0' */
+                   "call 0f\n"
+                 "0:pop  %0\n"
+                   "add  $_GLOBAL_OFFSET_TABLE_+[.-0b], %0\n"
+                   "mov  " SYS_SYSCALL_ENTRYPOINT "@GOT(%0), %0\n"
+                   : "=r"(entrypoint));
+      return entrypoint;
+    }
+
+    #define LSS_ENTRYPOINT ".bss\n"                                           \
+                           ".align 8\n"                                       \
+                           ".globl " SYS_SYSCALL_ENTRYPOINT "\n"              \
+                           ".common " SYS_SYSCALL_ENTRYPOINT ",8,8\n"         \
+                           ".previous\n"                                      \
+                           /* Check the SYS_SYSCALL_ENTRYPOINT vector      */ \
+                           "push %%eax\n"                                     \
+                           "call 10000f\n"                                    \
+                     "10000:pop  %%eax\n"                                     \
+                           "add  $_GLOBAL_OFFSET_TABLE_+[.-10000b], %%eax\n"  \
+                           "mov  " SYS_SYSCALL_ENTRYPOINT                     \
+                                 "@GOT(%%eax), %%eax\n"                       \
+                           "mov  0(%%eax), %%eax\n"                           \
+                           "test %%eax, %%eax\n"                              \
+                           "jz   10002f\n"                                    \
+                           "push %%eax\n"                                     \
+                           "call 10001f\n"                                    \
+                     "10001:pop  %%eax\n"                                     \
+                           "add  $(10003f-10001b), %%eax\n"                   \
+                           "xchg 4(%%esp), %%eax\n"                           \
+                           "ret\n"                                            \
+                     "10002:pop  %%eax\n"                                     \
+                           "int $0x80\n"                                      \
+                     "10003:\n"
+    #else
+    #define LSS_ENTRYPOINT "int $0x80\n"
+    #endif
+    #undef  LSS_BODY
+    #define LSS_BODY(type,args...)                                            \
+      long __res;                                                             \
+      __asm__ __volatile__("push %%ebx\n"                                     \
+                           "movl %2,%%ebx\n"                                  \
+                           LSS_ENTRYPOINT                                     \
+                           "pop %%ebx"                                        \
+                           args                                               \
+                           : "memory");                                       \
+      LSS_RETURN(type,__res)
+    #undef  _syscall0
+    #define _syscall0(type,name)                                              \
+      type LSS_NAME(name)(void) {                                             \
+        long __res;                                                           \
+        __asm__ volatile(LSS_ENTRYPOINT                                       \
+                         : "=a" (__res)                                       \
+                         : "0" (__NR_##name)                                  \
+                         : "memory");                                         \
+        LSS_RETURN(type,__res);                                               \
+      }
+    #undef  _syscall1
+    #define _syscall1(type,name,type1,arg1)                                   \
+      type LSS_NAME(name)(type1 arg1) {                                       \
+        LSS_BODY(type,                                                        \
+             : "=a" (__res)                                                   \
+             : "0" (__NR_##name), "ri" ((long)(arg1)));                       \
+      }
+    #undef  _syscall2
+    #define _syscall2(type,name,type1,arg1,type2,arg2)                        \
+      type LSS_NAME(name)(type1 arg1,type2 arg2) {                            \
+        LSS_BODY(type,                                                        \
+             : "=a" (__res)                                                   \
+             : "0" (__NR_##name),"ri" ((long)(arg1)), "c" ((long)(arg2)));    \
+      }
+    #undef  _syscall3
+    #define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3)             \
+      type LSS_NAME(name)(type1 arg1,type2 arg2,type3 arg3) {                 \
+        LSS_BODY(type,                                                        \
+             : "=a" (__res)                                                   \
+             : "0" (__NR_##name), "ri" ((long)(arg1)), "c" ((long)(arg2)),    \
+               "d" ((long)(arg3)));                                           \
+      }
+    #undef  _syscall4
+    #define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4)  \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4) {   \
+        LSS_BODY(type,                                                        \
+             : "=a" (__res)                                                   \
+             : "0" (__NR_##name), "ri" ((long)(arg1)), "c" ((long)(arg2)),    \
+               "d" ((long)(arg3)),"S" ((long)(arg4)));                        \
+      }
+    #undef  _syscall5
+    #define _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,  \
+                      type5,arg5)                                             \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4,     \
+                          type5 arg5) {                                       \
+        long __res;                                                           \
+        __asm__ __volatile__("push %%ebx\n"                                   \
+                             "movl %2,%%ebx\n"                                \
+                             "movl %1,%%eax\n"                                \
+                             LSS_ENTRYPOINT                                   \
+                             "pop  %%ebx"                                     \
+                             : "=a" (__res)                                   \
+                             : "i" (__NR_##name), "ri" ((long)(arg1)),        \
+                               "c" ((long)(arg2)), "d" ((long)(arg3)),        \
+                               "S" ((long)(arg4)), "D" ((long)(arg5))         \
+                             : "memory");                                     \
+        LSS_RETURN(type,__res);                                               \
+      }
+    #undef  _syscall6
+    #define _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,  \
+                      type5,arg5,type6,arg6)                                  \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4,     \
+                          type5 arg5, type6 arg6) {                           \
+        long __res;                                                           \
+        struct { long __a1; long __a6; } __s = { (long)arg1, (long) arg6 };   \
+        __asm__ __volatile__("push %%ebp\n"                                   \
+                             "push %%ebx\n"                                   \
+                             "movl 4(%2),%%ebp\n"                             \
+                             "movl 0(%2), %%ebx\n"                            \
+                             "movl %1,%%eax\n"                                \
+                             LSS_ENTRYPOINT                                   \
+                             "pop  %%ebx\n"                                   \
+                             "pop  %%ebp"                                     \
+                             : "=a" (__res)                                   \
+                             : "i" (__NR_##name),  "0" ((long)(&__s)),        \
+                               "c" ((long)(arg2)), "d" ((long)(arg3)),        \
+                               "S" ((long)(arg4)), "D" ((long)(arg5))         \
+                             : "memory");                                     \
+        LSS_RETURN(type,__res);                                               \
+      }
+    LSS_INLINE int LSS_NAME(clone)(int (*fn)(void *), void *child_stack,
+                                   int flags, void *arg, int *parent_tidptr,
+                                   void *newtls, int *child_tidptr) {
+      long __res;
+      __asm__ __volatile__(/* if (fn == NULL)
+                            *   return -EINVAL;
+                            */
+                           "movl   %3,%%ecx\n"
+                           "jecxz  1f\n"
+
+                           /* if (child_stack == NULL)
+                            *   return -EINVAL;
+                            */
+                           "movl   %4,%%ecx\n"
+                           "jecxz  1f\n"
+
+                           /* Set up alignment of the child stack:
+                            * child_stack = (child_stack & ~0xF) - 20;
+                            */
+                           "andl   $-16,%%ecx\n"
+                           "subl   $20,%%ecx\n"
+
+                           /* Push "arg" and "fn" onto the stack that will be
+                            * used by the child.
+                            */
+                           "movl   %6,%%eax\n"
+                           "movl   %%eax,4(%%ecx)\n"
+                           "movl   %3,%%eax\n"
+                           "movl   %%eax,(%%ecx)\n"
+
+                           /* %eax = syscall(%eax = __NR_clone,
+                            *                %ebx = flags,
+                            *                %ecx = child_stack,
+                            *                %edx = parent_tidptr,
+                            *                %esi = newtls,
+                            *                %edi = child_tidptr)
+                            * Also, make sure that %ebx gets preserved as it is
+                            * used in PIC mode.
+                            */
+                           "movl   %8,%%esi\n"
+                           "movl   %7,%%edx\n"
+                           "movl   %5,%%eax\n"
+                           "movl   %9,%%edi\n"
+                           "pushl  %%ebx\n"
+                           "movl   %%eax,%%ebx\n"
+                           "movl   %2,%%eax\n"
+                           LSS_ENTRYPOINT
+
+                           /* In the parent: restore %ebx
+                            * In the child:  move "fn" into %ebx
+                            */
+                           "popl   %%ebx\n"
+
+                           /* if (%eax != 0)
+                            *   return %eax;
+                            */
+                           "test   %%eax,%%eax\n"
+                           "jnz    1f\n"
+
+                           /* In the child, now. Terminate frame pointer chain.
+                            */
+                           "movl   $0,%%ebp\n"
+
+                           /* Call "fn". "arg" is already on the stack.
+                            */
+                           "call   *%%ebx\n"
+
+                           /* Call _exit(%ebx). Unfortunately older versions
+                            * of gcc restrict the number of arguments that can
+                            * be passed to asm(). So, we need to hard-code the
+                            * system call number.
+                            */
+                           "movl   %%eax,%%ebx\n"
+                           "movl   $1,%%eax\n"
+                           LSS_ENTRYPOINT
+
+                           /* Return to parent.
+                            */
+                         "1:\n"
+                           : "=a" (__res)
+                           : "0"(-EINVAL), "i"(__NR_clone),
+                             "m"(fn), "m"(child_stack), "m"(flags), "m"(arg),
+                             "m"(parent_tidptr), "m"(newtls), "m"(child_tidptr)
+                           : "memory", "ecx", "edx", "esi", "edi");
+      LSS_RETURN(int, __res);
+    }
+
+    LSS_INLINE _syscall1(int, set_thread_area, void *, u)
+    LSS_INLINE _syscall1(int, get_thread_area, void *, u)
+
+    LSS_INLINE void (*LSS_NAME(restore_rt)(void))(void) {
+      /* On i386, the kernel does not know how to return from a signal
+       * handler. Instead, it relies on user space to provide a
+       * restorer function that calls the {rt_,}sigreturn() system call.
+       * Unfortunately, we cannot just reference the glibc version of this
+       * function, as glibc goes out of its way to make it inaccessible.
+       */
+      void (*res)(void);
+      __asm__ __volatile__("call   2f\n"
+                         "0:.align 16\n"
+                         "1:movl   %1,%%eax\n"
+                           LSS_ENTRYPOINT
+                         "2:popl   %0\n"
+                           "addl   $(1b-0b),%0\n"
+                           : "=a" (res)
+                           : "i"  (__NR_rt_sigreturn));
+      return res;
+    }
+    LSS_INLINE void (*LSS_NAME(restore)(void))(void) {
+      /* On i386, the kernel does not know how to return from a signal
+       * handler. Instead, it relies on user space to provide a
+       * restorer function that calls the {rt_,}sigreturn() system call.
+       * Unfortunately, we cannot just reference the glibc version of this
+       * function, as glibc goes out of its way to make it inaccessible.
+       */
+      void (*res)(void);
+      __asm__ __volatile__("call   2f\n"
+                         "0:.align 16\n"
+                         "1:pop    %%eax\n"
+                           "movl   %1,%%eax\n"
+                           LSS_ENTRYPOINT
+                         "2:popl   %0\n"
+                           "addl   $(1b-0b),%0\n"
+                           : "=a" (res)
+                           : "i"  (__NR_sigreturn));
+      return res;
+    }
+  #elif defined(__x86_64__)
+    /* There are no known problems with any of the _syscallX() macros
+     * currently shipping for x86_64, but we still need to be able to define
+     * our own version so that we can override the location of the errno
+     * location (e.g. when using the clone() system call with the CLONE_VM
+     * option).
+     */
+    #undef LSS_ENTRYPOINT
+    #ifdef SYS_SYSCALL_ENTRYPOINT
+    static inline void (**LSS_NAME(get_syscall_entrypoint)(void))(void) {
+      void (**entrypoint)(void);
+      asm volatile(".bss\n"
+                   ".align 8\n"
+                   ".globl " SYS_SYSCALL_ENTRYPOINT "\n"
+                   ".common " SYS_SYSCALL_ENTRYPOINT ",8,8\n"
+                   ".previous\n"
+                   "mov " SYS_SYSCALL_ENTRYPOINT "@GOTPCREL(%%rip), %0\n"
+                   : "=r"(entrypoint));
+      return entrypoint;
+    }
+
+    #define LSS_ENTRYPOINT                                                    \
+              ".bss\n"                                                        \
+              ".align 8\n"                                                    \
+              ".globl " SYS_SYSCALL_ENTRYPOINT "\n"                           \
+              ".common " SYS_SYSCALL_ENTRYPOINT ",8,8\n"                      \
+              ".previous\n"                                                   \
+              "mov " SYS_SYSCALL_ENTRYPOINT "@GOTPCREL(%%rip), %%rcx\n"       \
+              "mov  0(%%rcx), %%rcx\n"                                        \
+              "test %%rcx, %%rcx\n"                                           \
+              "jz   10001f\n"                                                 \
+              "call *%%rcx\n"                                                 \
+              "jmp  10002f\n"                                                 \
+        "10001:syscall\n"                                                     \
+        "10002:\n"
+
+    #else
+    #define LSS_ENTRYPOINT "syscall\n"
+    #endif
+
+    /* The x32 ABI has 32 bit longs, but the syscall interface is 64 bit.
+     * We need to explicitly cast to an unsigned 64 bit type to avoid implicit
+     * sign extension.  We can't cast pointers directly because those are
+     * 32 bits, and gcc will dump ugly warnings about casting from a pointer
+     * to an integer of a different size.
+     */
+    #undef  LSS_SYSCALL_ARG
+    #define LSS_SYSCALL_ARG(a) ((uint64_t)(uintptr_t)(a))
+    #undef  _LSS_RETURN
+    #define _LSS_RETURN(type, res, cast)                                      \
+      do {                                                                    \
+        if ((uint64_t)(res) >= (uint64_t)(-4095)) {                           \
+          LSS_ERRNO = -(res);                                                 \
+          res = -1;                                                           \
+        }                                                                     \
+        return (type)(cast)(res);                                             \
+      } while (0)
+    #undef  LSS_RETURN
+    #define LSS_RETURN(type, res) _LSS_RETURN(type, res, uintptr_t)
+
+    #undef  _LSS_BODY
+    #define _LSS_BODY(nr, type, name, cast, ...)                              \
+          long long __res;                                                    \
+          __asm__ __volatile__(LSS_BODY_ASM##nr LSS_ENTRYPOINT                \
+            : "=a" (__res)                                                    \
+            : "0" (__NR_##name) LSS_BODY_ARG##nr(__VA_ARGS__)                 \
+            : LSS_BODY_CLOBBER##nr "r11", "rcx", "memory");                   \
+          _LSS_RETURN(type, __res, cast)
+    #undef  LSS_BODY
+    #define LSS_BODY(nr, type, name, args...) \
+      _LSS_BODY(nr, type, name, uintptr_t, ## args)
+
+    #undef  LSS_BODY_ASM0
+    #undef  LSS_BODY_ASM1
+    #undef  LSS_BODY_ASM2
+    #undef  LSS_BODY_ASM3
+    #undef  LSS_BODY_ASM4
+    #undef  LSS_BODY_ASM5
+    #undef  LSS_BODY_ASM6
+    #define LSS_BODY_ASM0
+    #define LSS_BODY_ASM1 LSS_BODY_ASM0
+    #define LSS_BODY_ASM2 LSS_BODY_ASM1
+    #define LSS_BODY_ASM3 LSS_BODY_ASM2
+    #define LSS_BODY_ASM4 LSS_BODY_ASM3 "movq %5,%%r10;"
+    #define LSS_BODY_ASM5 LSS_BODY_ASM4 "movq %6,%%r8;"
+    #define LSS_BODY_ASM6 LSS_BODY_ASM5 "movq %7,%%r9;"
+
+    #undef  LSS_BODY_CLOBBER0
+    #undef  LSS_BODY_CLOBBER1
+    #undef  LSS_BODY_CLOBBER2
+    #undef  LSS_BODY_CLOBBER3
+    #undef  LSS_BODY_CLOBBER4
+    #undef  LSS_BODY_CLOBBER5
+    #undef  LSS_BODY_CLOBBER6
+    #define LSS_BODY_CLOBBER0
+    #define LSS_BODY_CLOBBER1 LSS_BODY_CLOBBER0
+    #define LSS_BODY_CLOBBER2 LSS_BODY_CLOBBER1
+    #define LSS_BODY_CLOBBER3 LSS_BODY_CLOBBER2
+    #define LSS_BODY_CLOBBER4 LSS_BODY_CLOBBER3 "r10",
+    #define LSS_BODY_CLOBBER5 LSS_BODY_CLOBBER4 "r8",
+    #define LSS_BODY_CLOBBER6 LSS_BODY_CLOBBER5 "r9",
+
+    #undef  LSS_BODY_ARG0
+    #undef  LSS_BODY_ARG1
+    #undef  LSS_BODY_ARG2
+    #undef  LSS_BODY_ARG3
+    #undef  LSS_BODY_ARG4
+    #undef  LSS_BODY_ARG5
+    #undef  LSS_BODY_ARG6
+    #define LSS_BODY_ARG0()
+    #define LSS_BODY_ARG1(arg1) \
+      LSS_BODY_ARG0(), "D" (arg1)
+    #define LSS_BODY_ARG2(arg1, arg2) \
+      LSS_BODY_ARG1(arg1), "S" (arg2)
+    #define LSS_BODY_ARG3(arg1, arg2, arg3) \
+      LSS_BODY_ARG2(arg1, arg2), "d" (arg3)
+    #define LSS_BODY_ARG4(arg1, arg2, arg3, arg4) \
+      LSS_BODY_ARG3(arg1, arg2, arg3), "r" (arg4)
+    #define LSS_BODY_ARG5(arg1, arg2, arg3, arg4, arg5) \
+      LSS_BODY_ARG4(arg1, arg2, arg3, arg4), "r" (arg5)
+    #define LSS_BODY_ARG6(arg1, arg2, arg3, arg4, arg5, arg6) \
+      LSS_BODY_ARG5(arg1, arg2, arg3, arg4, arg5), "r" (arg6)
+
+    #undef _syscall0
+    #define _syscall0(type,name)                                              \
+      type LSS_NAME(name)(void) {                                             \
+        LSS_BODY(0, type, name);                                              \
+      }
+    #undef _syscall1
+    #define _syscall1(type,name,type1,arg1)                                   \
+      type LSS_NAME(name)(type1 arg1) {                                       \
+        LSS_BODY(1, type, name, LSS_SYSCALL_ARG(arg1));                       \
+      }
+    #undef _syscall2
+    #define _syscall2(type,name,type1,arg1,type2,arg2)                        \
+      type LSS_NAME(name)(type1 arg1, type2 arg2) {                           \
+        LSS_BODY(2, type, name, LSS_SYSCALL_ARG(arg1), LSS_SYSCALL_ARG(arg2));\
+      }
+    #undef _syscall3
+    #define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3)             \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3) {               \
+        LSS_BODY(3, type, name, LSS_SYSCALL_ARG(arg1), LSS_SYSCALL_ARG(arg2), \
+                                LSS_SYSCALL_ARG(arg3));                       \
+      }
+    #undef _syscall4
+    #define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4)  \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4) {   \
+        LSS_BODY(4, type, name, LSS_SYSCALL_ARG(arg1), LSS_SYSCALL_ARG(arg2), \
+                                LSS_SYSCALL_ARG(arg3), LSS_SYSCALL_ARG(arg4));\
+      }
+    #undef _syscall5
+    #define _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,  \
+                      type5,arg5)                                             \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4,     \
+                          type5 arg5) {                                       \
+        LSS_BODY(5, type, name, LSS_SYSCALL_ARG(arg1), LSS_SYSCALL_ARG(arg2), \
+                                LSS_SYSCALL_ARG(arg3), LSS_SYSCALL_ARG(arg4), \
+                                LSS_SYSCALL_ARG(arg5));                       \
+      }
+    #undef _syscall6
+    #define _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,  \
+                      type5,arg5,type6,arg6)                                  \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4,     \
+                          type5 arg5, type6 arg6) {                           \
+        LSS_BODY(6, type, name, LSS_SYSCALL_ARG(arg1), LSS_SYSCALL_ARG(arg2), \
+                                LSS_SYSCALL_ARG(arg3), LSS_SYSCALL_ARG(arg4), \
+                                LSS_SYSCALL_ARG(arg5), LSS_SYSCALL_ARG(arg6));\
+      }
+    LSS_INLINE int LSS_NAME(clone)(int (*fn)(void *), void *child_stack,
+                                   int flags, void *arg, int *parent_tidptr,
+                                   void *newtls, int *child_tidptr) {
+      long long __res;
+      {
+        __asm__ __volatile__(/* if (fn == NULL)
+                              *   return -EINVAL;
+                              */
+                             "testq  %4,%4\n"
+                             "jz     1f\n"
+
+                             /* if (child_stack == NULL)
+                              *   return -EINVAL;
+                              */
+                             "testq  %5,%5\n"
+                             "jz     1f\n"
+
+                             /* childstack -= 2*sizeof(void *);
+                              */
+                             "subq   $16,%5\n"
+
+                             /* Push "arg" and "fn" onto the stack that will be
+                              * used by the child.
+                              */
+                             "movq   %7,8(%5)\n"
+                             "movq   %4,0(%5)\n"
+
+                             /* %rax = syscall(%rax = __NR_clone,
+                              *                %rdi = flags,
+                              *                %rsi = child_stack,
+                              *                %rdx = parent_tidptr,
+                              *                %r8  = new_tls,
+                              *                %r10 = child_tidptr)
+                              */
+                             "movq   %2,%%rax\n"
+                             "movq   %9,%%r8\n"
+                             "movq   %10,%%r10\n"
+                             LSS_ENTRYPOINT
+
+                             /* if (%rax != 0)
+                              *   return;
+                              */
+                             "testq  %%rax,%%rax\n"
+                             "jnz    1f\n"
+
+                             /* In the child. Terminate frame pointer chain.
+                              */
+                             "xorq   %%rbp,%%rbp\n"
+
+                             /* Call "fn(arg)".
+                              */
+                             "popq   %%rax\n"
+                             "popq   %%rdi\n"
+                             "call   *%%rax\n"
+
+                             /* Call _exit(%ebx).
+                              */
+                             "movq   %%rax,%%rdi\n"
+                             "movq   %3,%%rax\n"
+                             LSS_ENTRYPOINT
+
+                             /* Return to parent.
+                              */
+                           "1:\n"
+                             : "=a" (__res)
+                             : "0"(-EINVAL), "i"(__NR_clone), "i"(__NR_exit),
+                               "r"(LSS_SYSCALL_ARG(fn)),
+                               "S"(LSS_SYSCALL_ARG(child_stack)),
+                               "D"(LSS_SYSCALL_ARG(flags)),
+                               "r"(LSS_SYSCALL_ARG(arg)),
+                               "d"(LSS_SYSCALL_ARG(parent_tidptr)),
+                               "r"(LSS_SYSCALL_ARG(newtls)),
+                               "r"(LSS_SYSCALL_ARG(child_tidptr))
+                             : "memory", "r8", "r10", "r11", "rcx");
+      }
+      LSS_RETURN(int, __res);
+    }
+    LSS_INLINE _syscall2(int, arch_prctl, int, c, void *, a)
+
+    LSS_INLINE void (*LSS_NAME(restore_rt)(void))(void) {
+      /* On x86-64, the kernel does not know how to return from
+       * a signal handler. Instead, it relies on user space to provide a
+       * restorer function that calls the rt_sigreturn() system call.
+       * Unfortunately, we cannot just reference the glibc version of this
+       * function, as glibc goes out of its way to make it inaccessible.
+       */
+      long long res;
+      __asm__ __volatile__("jmp    2f\n"
+                           ".align 16\n"
+                         "1:movq   %1,%%rax\n"
+                           LSS_ENTRYPOINT
+                         "2:leaq   1b(%%rip),%0\n"
+                           : "=r" (res)
+                           : "i"  (__NR_rt_sigreturn));
+      return (void (*)(void))(uintptr_t)res;
+    }
+  #elif defined(__ARM_ARCH_3__)
+    /* Most definitions of _syscallX() neglect to mark "memory" as being
+     * clobbered. This causes problems with compilers, that do a better job
+     * at optimizing across __asm__ calls.
+     * So, we just have to redefine all of the _syscallX() macros.
+     */
+    #undef LSS_REG
+    #define LSS_REG(r,a) register long __r##r __asm__("r"#r) = (long)a
+    #undef  LSS_BODY
+    #define LSS_BODY(type,name,args...)                                       \
+          register long __res_r0 __asm__("r0");                               \
+          long __res;                                                         \
+          __asm__ __volatile__ (__syscall(name)                               \
+                                : "=r"(__res_r0) : args : "lr", "memory");    \
+          __res = __res_r0;                                                   \
+          LSS_RETURN(type, __res)
+    #undef _syscall0
+    #define _syscall0(type, name)                                             \
+      type LSS_NAME(name)(void) {                                             \
+        LSS_BODY(type, name);                                                 \
+      }
+    #undef _syscall1
+    #define _syscall1(type, name, type1, arg1)                                \
+      type LSS_NAME(name)(type1 arg1) {                                       \
+        LSS_REG(0, arg1); LSS_BODY(type, name, "r"(__r0));                    \
+      }
+    #undef _syscall2
+    #define _syscall2(type, name, type1, arg1, type2, arg2)                   \
+      type LSS_NAME(name)(type1 arg1, type2 arg2) {                           \
+        LSS_REG(0, arg1); LSS_REG(1, arg2);                                   \
+        LSS_BODY(type, name, "r"(__r0), "r"(__r1));                           \
+      }
+    #undef _syscall3
+    #define _syscall3(type, name, type1, arg1, type2, arg2, type3, arg3)      \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3) {               \
+        LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3);                 \
+        LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2));                \
+      }
+    #undef _syscall4
+    #define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4)  \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4) {   \
+        LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3);                 \
+        LSS_REG(3, arg4);                                                     \
+        LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2), "r"(__r3));     \
+      }
+    #undef _syscall5
+    #define _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,  \
+                      type5,arg5)                                             \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4,     \
+                          type5 arg5) {                                       \
+        LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3);                 \
+        LSS_REG(3, arg4); LSS_REG(4, arg5);                                   \
+        LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2), "r"(__r3),      \
+                             "r"(__r4));                                      \
+      }
+    #undef _syscall6
+    #define _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,  \
+                      type5,arg5,type6,arg6)                                  \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4,     \
+                          type5 arg5, type6 arg6) {                           \
+        LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3);                 \
+        LSS_REG(3, arg4); LSS_REG(4, arg5); LSS_REG(5, arg6);                 \
+        LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2), "r"(__r3),      \
+                             "r"(__r4), "r"(__r5));                           \
+      }
+    LSS_INLINE int LSS_NAME(clone)(int (*fn)(void *), void *child_stack,
+                                   int flags, void *arg, int *parent_tidptr,
+                                   void *newtls, int *child_tidptr) {
+      long __res;
+      {
+        register int   __flags __asm__("r0") = flags;
+        register void *__stack __asm__("r1") = child_stack;
+        register void *__ptid  __asm__("r2") = parent_tidptr;
+        register void *__tls   __asm__("r3") = newtls;
+        register int  *__ctid  __asm__("r4") = child_tidptr;
+        __asm__ __volatile__(/* if (fn == NULL || child_stack == NULL)
+                              *   return -EINVAL;
+                              */
+                             "cmp   %2,#0\n"
+                             "cmpne %3,#0\n"
+                             "moveq %0,%1\n"
+                             "beq   1f\n"
+
+                             /* Push "arg" and "fn" onto the stack that will be
+                              * used by the child.
+                              */
+                             "str   %5,[%3,#-4]!\n"
+                             "str   %2,[%3,#-4]!\n"
+
+                             /* %r0 = syscall(%r0 = flags,
+                              *               %r1 = child_stack,
+                              *               %r2 = parent_tidptr,
+                              *               %r3 = newtls,
+                              *               %r4 = child_tidptr)
+                              */
+                             __syscall(clone)"\n"
+
+                             /* if (%r0 != 0)
+                              *   return %r0;
+                              */
+                             "movs  %0,r0\n"
+                             "bne   1f\n"
+
+                             /* In the child, now. Call "fn(arg)".
+                              */
+                             "ldr   r0,[sp, #4]\n"
+                             "mov   lr,pc\n"
+                             "ldr   pc,[sp]\n"
+
+                             /* Call _exit(%r0).
+                              */
+                             __syscall(exit)"\n"
+                           "1:\n"
+                             : "=r" (__res)
+                             : "i"(-EINVAL),
+                               "r"(fn), "r"(__stack), "r"(__flags), "r"(arg),
+                               "r"(__ptid), "r"(__tls), "r"(__ctid)
+                             : "cc", "lr", "memory");
+      }
+      LSS_RETURN(int, __res);
+    }
+  #elif defined(__ARM_EABI__)
+    /* Most definitions of _syscallX() neglect to mark "memory" as being
+     * clobbered. This causes problems with compilers, that do a better job
+     * at optimizing across __asm__ calls.
+     * So, we just have to redefine all fo the _syscallX() macros.
+     */
+    #undef LSS_REG
+    #define LSS_REG(r,a) register long __r##r __asm__("r"#r) = (long)a
+    #undef  LSS_BODY
+    #define LSS_BODY(type,name,args...)                                       \
+          register long __res_r0 __asm__("r0");                               \
+          long __res;                                                         \
+          __asm__ __volatile__ ("push {r7}\n"                                 \
+                                "mov r7, %1\n"                                \
+                                "swi 0x0\n"                                   \
+                                "pop {r7}\n"                                  \
+                                : "=r"(__res_r0)                              \
+                                : "i"(__NR_##name) , ## args                  \
+                                : "lr", "memory");                            \
+          __res = __res_r0;                                                   \
+          LSS_RETURN(type, __res)
+    #undef _syscall0
+    #define _syscall0(type, name)                                             \
+      type LSS_NAME(name)(void) {                                             \
+        LSS_BODY(type, name);                                                 \
+      }
+    #undef _syscall1
+    #define _syscall1(type, name, type1, arg1)                                \
+      type LSS_NAME(name)(type1 arg1) {                                       \
+        LSS_REG(0, arg1); LSS_BODY(type, name, "r"(__r0));                    \
+      }
+    #undef _syscall2
+    #define _syscall2(type, name, type1, arg1, type2, arg2)                   \
+      type LSS_NAME(name)(type1 arg1, type2 arg2) {                           \
+        LSS_REG(0, arg1); LSS_REG(1, arg2);                                   \
+        LSS_BODY(type, name, "r"(__r0), "r"(__r1));                           \
+      }
+    #undef _syscall3
+    #define _syscall3(type, name, type1, arg1, type2, arg2, type3, arg3)      \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3) {               \
+        LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3);                 \
+        LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2));                \
+      }
+    #undef _syscall4
+    #define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4)  \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4) {   \
+        LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3);                 \
+        LSS_REG(3, arg4);                                                     \
+        LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2), "r"(__r3));     \
+      }
+    #undef _syscall5
+    #define _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,  \
+                      type5,arg5)                                             \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4,     \
+                          type5 arg5) {                                       \
+        LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3);                 \
+        LSS_REG(3, arg4); LSS_REG(4, arg5);                                   \
+        LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2), "r"(__r3),      \
+                             "r"(__r4));                                      \
+      }
+    #undef _syscall6
+    #define _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,  \
+                      type5,arg5,type6,arg6)                                  \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4,     \
+                          type5 arg5, type6 arg6) {                           \
+        LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3);                 \
+        LSS_REG(3, arg4); LSS_REG(4, arg5); LSS_REG(5, arg6);                 \
+        LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2), "r"(__r3),      \
+                             "r"(__r4), "r"(__r5));                           \
+      }
+    LSS_INLINE int LSS_NAME(clone)(int (*fn)(void *), void *child_stack,
+                                   int flags, void *arg, int *parent_tidptr,
+                                   void *newtls, int *child_tidptr) {
+      long __res;
+      if (fn == NULL || child_stack == NULL) {
+        __res = -EINVAL;
+        LSS_RETURN(int, __res);
+      }
+
+      /* Push "arg" and "fn" onto the stack that will be
+       * used by the child.
+       */
+      {
+        uintptr_t* cstack = (uintptr_t*)child_stack - 2;
+        cstack[0] = (uintptr_t)fn;
+        cstack[1] = (uintptr_t)arg;
+        child_stack = cstack;
+      }
+      {
+        register int   __flags __asm__("r0") = flags;
+        register void *__stack __asm__("r1") = child_stack;
+        register void *__ptid  __asm__("r2") = parent_tidptr;
+        register void *__tls   __asm__("r3") = newtls;
+        register int  *__ctid  __asm__("r4") = child_tidptr;
+        __asm__ __volatile__(
+#ifdef __thumb2__
+            "push {r7}\n"
+#endif
+            /* %r0 = syscall(%r0 = flags,
+             *               %r1 = child_stack,
+             *               %r2 = parent_tidptr,
+             *               %r3 = newtls,
+             *               %r4 = child_tidptr)
+             */
+            "mov r7, %6\n"
+            "swi 0x0\n"
+
+            /* if (%r0 != 0)
+             *   return %r0;
+             */
+            "cmp   r0, #0\n"
+            "bne   1f\n"
+
+            /* In the child, now. Call "fn(arg)".
+             */
+            "ldr   r0,[sp, #4]\n"
+
+            "ldr   lr,[sp]\n"
+            "blx   lr\n"
+
+            /* Call _exit(%r0).
+             */
+            "mov r7, %7\n"
+            "swi 0x0\n"
+            /* Unreachable */
+            "bkpt #0\n"
+         "1:\n"
+#ifdef __thumb2__
+            "pop {r7}\n"
+#endif
+            "movs  %0,r0\n"
+            : "=r"(__res)
+            : "r"(__stack), "r"(__flags), "r"(__ptid), "r"(__tls), "r"(__ctid),
+              "i"(__NR_clone), "i"(__NR_exit)
+            : "cc", "lr", "memory"
+#ifndef __thumb2__
+            , "r7"
+#endif
+            );
+      }
+      LSS_RETURN(int, __res);
+    }
+  #elif defined(__aarch64__)
+    /* Most definitions of _syscallX() neglect to mark "memory" as being
+     * clobbered. This causes problems with compilers, that do a better job
+     * at optimizing across __asm__ calls.
+     * So, we just have to redefine all of the _syscallX() macros.
+     */
+    #undef LSS_REG
+    #define LSS_REG(r,a) register int64_t __r##r __asm__("x"#r) = (int64_t)a
+    #undef  LSS_BODY
+    #define LSS_BODY(type,name,args...)                                       \
+          register int64_t __res_x0 __asm__("x0");                            \
+          int64_t __res;                                                      \
+          __asm__ __volatile__ ("mov x8, %1\n"                                \
+                                "svc 0x0\n"                                   \
+                                : "=r"(__res_x0)                              \
+                                : "i"(__NR_##name) , ## args                  \
+                                : "x8", "memory");                            \
+          __res = __res_x0;                                                   \
+          LSS_RETURN(type, __res)
+    #undef _syscall0
+    #define _syscall0(type, name)                                             \
+      type LSS_NAME(name)(void) {                                             \
+        LSS_BODY(type, name);                                                 \
+      }
+    #undef _syscall1
+    #define _syscall1(type, name, type1, arg1)                                \
+      type LSS_NAME(name)(type1 arg1) {                                       \
+        LSS_REG(0, arg1); LSS_BODY(type, name, "r"(__r0));                    \
+      }
+    #undef _syscall2
+    #define _syscall2(type, name, type1, arg1, type2, arg2)                   \
+      type LSS_NAME(name)(type1 arg1, type2 arg2) {                           \
+        LSS_REG(0, arg1); LSS_REG(1, arg2);                                   \
+        LSS_BODY(type, name, "r"(__r0), "r"(__r1));                           \
+      }
+    #undef _syscall3
+    #define _syscall3(type, name, type1, arg1, type2, arg2, type3, arg3)      \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3) {               \
+        LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3);                 \
+        LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2));                \
+      }
+    #undef _syscall4
+    #define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4)  \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4) {   \
+        LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3);                 \
+        LSS_REG(3, arg4);                                                     \
+        LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2), "r"(__r3));     \
+      }
+    #undef _syscall5
+    #define _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,  \
+                      type5,arg5)                                             \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4,     \
+                          type5 arg5) {                                       \
+        LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3);                 \
+        LSS_REG(3, arg4); LSS_REG(4, arg5);                                   \
+        LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2), "r"(__r3),      \
+                             "r"(__r4));                                      \
+      }
+    #undef _syscall6
+    #define _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,  \
+                      type5,arg5,type6,arg6)                                  \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4,     \
+                          type5 arg5, type6 arg6) {                           \
+        LSS_REG(0, arg1); LSS_REG(1, arg2); LSS_REG(2, arg3);                 \
+        LSS_REG(3, arg4); LSS_REG(4, arg5); LSS_REG(5, arg6);                 \
+        LSS_BODY(type, name, "r"(__r0), "r"(__r1), "r"(__r2), "r"(__r3),      \
+                             "r"(__r4), "r"(__r5));                           \
+      }
+
+    LSS_INLINE int LSS_NAME(clone)(int (*fn)(void *), void *child_stack,
+                                   int flags, void *arg, int *parent_tidptr,
+                                   void *newtls, int *child_tidptr) {
+      int64_t __res;
+      {
+        register uint64_t __flags __asm__("x0") = flags;
+        register void *__stack __asm__("x1") = child_stack;
+        register void *__ptid  __asm__("x2") = parent_tidptr;
+        register void *__tls   __asm__("x3") = newtls;
+        register int  *__ctid  __asm__("x4") = child_tidptr;
+        __asm__ __volatile__(/* Push "arg" and "fn" onto the stack that will be
+                              * used by the child.
+                              */
+                             "stp     %1, %4, [%2, #-16]!\n"
+
+                             /* %x0 = syscall(%x0 = flags,
+                              *               %x1 = child_stack,
+                              *               %x2 = parent_tidptr,
+                              *               %x3 = newtls,
+                              *               %x4 = child_tidptr)
+                              */
+                             "mov     x8, %8\n"
+                             "svc     0x0\n"
+
+                             /* if (%r0 != 0)
+                              *   return %r0;
+                              */
+                             "mov     %0, x0\n"
+                             "cbnz    x0, 1f\n"
+
+                             /* In the child, now. Call "fn(arg)".
+                              */
+                             "ldp     x1, x0, [sp], #16\n"
+                             "blr     x1\n"
+
+                             /* Call _exit(%r0).
+                              */
+                             "mov     x8, %9\n"
+                             "svc     0x0\n"
+                           "1:\n"
+                             : "=r" (__res)
+                             : "r"(fn), "r"(__stack), "r"(__flags), "r"(arg),
+                               "r"(__ptid), "r"(__tls), "r"(__ctid),
+                               "i"(__NR_clone), "i"(__NR_exit)
+                             : "cc", "x8", "memory");
+      }
+      LSS_RETURN(int, __res);
+    }
+  #elif defined(__mips__)
+    #undef LSS_REG
+    #define LSS_REG(r,a) register unsigned long __r##r __asm__("$"#r) =       \
+                                 (unsigned long)(a)
+    #undef  LSS_BODY
+    #undef LSS_SYSCALL_CLOBBERS
+    #if _MIPS_SIM == _MIPS_SIM_ABI32
+    #define LSS_SYSCALL_CLOBBERS "$1", "$3", "$8", "$9", "$10",               \
+                                 "$11", "$12", "$13", "$14", "$15",           \
+                                 "$24", "$25", "hi", "lo", "memory"
+    #else
+    #define LSS_SYSCALL_CLOBBERS "$1", "$3", "$10", "$11", "$12",             \
+                                 "$13", "$14", "$15", "$24", "$25",           \
+                                 "hi", "lo", "memory"
+    #endif
+    #define LSS_BODY(type,name,r7,...)                                        \
+          register unsigned long __v0 __asm__("$2") = __NR_##name;            \
+          __asm__ __volatile__ ("syscall\n"                                   \
+                                : "=r"(__v0), r7 (__r7)                       \
+                                : "0"(__v0), ##__VA_ARGS__                    \
+                                : LSS_SYSCALL_CLOBBERS);                      \
+          LSS_RETURN(type, __v0, __r7)
+    #undef _syscall0
+    #define _syscall0(type, name)                                             \
+      type LSS_NAME(name)(void) {                                             \
+        register unsigned long __r7 __asm__("$7");                            \
+        LSS_BODY(type, name, "=r");                                           \
+      }
+    #undef _syscall1
+    #define _syscall1(type, name, type1, arg1)                                \
+      type LSS_NAME(name)(type1 arg1) {                                       \
+        register unsigned long __r7 __asm__("$7");                            \
+        LSS_REG(4, arg1); LSS_BODY(type, name, "=r", "r"(__r4));              \
+      }
+    #undef _syscall2
+    #define _syscall2(type, name, type1, arg1, type2, arg2)                   \
+      type LSS_NAME(name)(type1 arg1, type2 arg2) {                           \
+        register unsigned long __r7 __asm__("$7");                            \
+        LSS_REG(4, arg1); LSS_REG(5, arg2);                                   \
+        LSS_BODY(type, name, "=r", "r"(__r4), "r"(__r5));                     \
+      }
+    #undef _syscall3
+    #define _syscall3(type, name, type1, arg1, type2, arg2, type3, arg3)      \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3) {               \
+        register unsigned long __r7 __asm__("$7");                            \
+        LSS_REG(4, arg1); LSS_REG(5, arg2); LSS_REG(6, arg3);                 \
+        LSS_BODY(type, name, "=r", "r"(__r4), "r"(__r5), "r"(__r6));          \
+      }
+    #undef _syscall4
+    #define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4)  \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4) {   \
+        LSS_REG(4, arg1); LSS_REG(5, arg2); LSS_REG(6, arg3);                 \
+        LSS_REG(7, arg4);                                                     \
+        LSS_BODY(type, name, "+r", "r"(__r4), "r"(__r5), "r"(__r6));          \
+      }
+    #undef _syscall5
+    #if _MIPS_SIM == _MIPS_SIM_ABI32
+    /* The old 32bit MIPS system call API passes the fifth and sixth argument
+     * on the stack, whereas the new APIs use registers "r8" and "r9".
+     */
+    #define _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,  \
+                      type5,arg5)                                             \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4,     \
+                          type5 arg5) {                                       \
+        LSS_REG(4, arg1); LSS_REG(5, arg2); LSS_REG(6, arg3);                 \
+        LSS_REG(7, arg4);                                                     \
+        register unsigned long __v0 __asm__("$2") = __NR_##name;              \
+        __asm__ __volatile__ (".set noreorder\n"                              \
+                              "subu  $29, 32\n"                               \
+                              "sw    %5, 16($29)\n"                           \
+                              "syscall\n"                                     \
+                              "addiu $29, 32\n"                               \
+                              ".set reorder\n"                                \
+                              : "+r"(__v0), "+r" (__r7)                       \
+                              : "r"(__r4), "r"(__r5),                         \
+                                "r"(__r6), "r" ((unsigned long)arg5)          \
+                              : "$8", "$9", "$10", "$11", "$12",              \
+                                "$13", "$14", "$15", "$24", "$25",            \
+                                "memory");                                    \
+        LSS_RETURN(type, __v0, __r7);                                         \
+      }
+    #else
+    #define _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,  \
+                      type5,arg5)                                             \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4,     \
+                          type5 arg5) {                                       \
+        LSS_REG(4, arg1); LSS_REG(5, arg2); LSS_REG(6, arg3);                 \
+        LSS_REG(7, arg4); LSS_REG(8, arg5);                                   \
+        LSS_BODY(type, name, "+r", "r"(__r4), "r"(__r5), "r"(__r6),           \
+                 "r"(__r8));                                                  \
+      }
+    #endif
+    #undef _syscall6
+    #if _MIPS_SIM == _MIPS_SIM_ABI32
+    /* The old 32bit MIPS system call API passes the fifth and sixth argument
+     * on the stack, whereas the new APIs use registers "r8" and "r9".
+     */
+    #define _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,  \
+                      type5,arg5,type6,arg6)                                  \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4,     \
+                          type5 arg5, type6 arg6) {                           \
+        LSS_REG(4, arg1); LSS_REG(5, arg2); LSS_REG(6, arg3);                 \
+        LSS_REG(7, arg4);                                                     \
+        register unsigned long __v0 __asm__("$2") = __NR_##name;              \
+        __asm__ __volatile__ (".set noreorder\n"                              \
+                              "subu  $29, 32\n"                               \
+                              "sw    %5, 16($29)\n"                           \
+                              "sw    %6, 20($29)\n"                           \
+                              "syscall\n"                                     \
+                              "addiu $29, 32\n"                               \
+                              ".set reorder\n"                                \
+                              : "+r"(__v0), "+r" (__r7)                       \
+                              : "r"(__r4), "r"(__r5),                         \
+                                "r"(__r6), "r" ((unsigned long)arg5),         \
+                                "r" ((unsigned long)arg6)                     \
+                              : "$8", "$9", "$10", "$11", "$12",              \
+                                "$13", "$14", "$15", "$24", "$25",            \
+                                "memory");                                    \
+        LSS_RETURN(type, __v0, __r7);                                         \
+      }
+    #else
+    #define _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,  \
+                      type5,arg5,type6,arg6)                                  \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4,     \
+                          type5 arg5,type6 arg6) {                            \
+        LSS_REG(4, arg1); LSS_REG(5, arg2); LSS_REG(6, arg3);                 \
+        LSS_REG(7, arg4); LSS_REG(8, arg5); LSS_REG(9, arg6);                 \
+        LSS_BODY(type, name, "+r", "r"(__r4), "r"(__r5), "r"(__r6),           \
+                 "r"(__r8), "r"(__r9));                                       \
+      }
+    #endif
+    LSS_INLINE int LSS_NAME(clone)(int (*fn)(void *), void *child_stack,
+                                   int flags, void *arg, int *parent_tidptr,
+                                   void *newtls, int *child_tidptr) {
+      register unsigned long __v0 __asm__("$2") = -EINVAL;
+      register unsigned long __r7 __asm__("$7") = (unsigned long)newtls;
+      {
+        register int   __flags __asm__("$4") = flags;
+        register void *__stack __asm__("$5") = child_stack;
+        register void *__ptid  __asm__("$6") = parent_tidptr;
+        register int  *__ctid  __asm__("$8") = child_tidptr;
+        __asm__ __volatile__(
+          #if _MIPS_SIM == _MIPS_SIM_ABI32 && _MIPS_SZPTR == 32
+                             "subu  $29,24\n"
+          #elif _MIPS_SIM == _MIPS_SIM_NABI32
+                             "sub   $29,16\n"
+          #else
+                             "dsubu $29,16\n"
+          #endif
+
+                             /* if (fn == NULL || child_stack == NULL)
+                              *   return -EINVAL;
+                              */
+                             "beqz  %4,1f\n"
+                             "beqz  %5,1f\n"
+
+                             /* Push "arg" and "fn" onto the stack that will be
+                              * used by the child.
+                              */
+          #if _MIPS_SIM == _MIPS_SIM_ABI32 && _MIPS_SZPTR == 32
+                             "subu  %5,32\n"
+                             "sw    %4,0(%5)\n"
+                             "sw    %7,4(%5)\n"
+          #elif _MIPS_SIM == _MIPS_SIM_NABI32
+                             "sub   %5,32\n"
+                             "sw    %4,0(%5)\n"
+                             "sw    %7,8(%5)\n"
+          #else
+                             "dsubu %5,32\n"
+                             "sd    %4,0(%5)\n"
+                             "sd    %7,8(%5)\n"
+          #endif
+
+                             /* $7 = syscall($4 = flags,
+                              *              $5 = child_stack,
+                              *              $6 = parent_tidptr,
+                              *              $7 = newtls,
+                              *              $8 = child_tidptr)
+                              */
+                             "li    $2,%2\n"
+                             "syscall\n"
+
+                             /* if ($7 != 0)
+                              *   return $2;
+                              */
+                             "bnez  $7,1f\n"
+                             "bnez  $2,1f\n"
+
+                             /* In the child, now. Call "fn(arg)".
+                              */
+          #if _MIPS_SIM == _MIPS_SIM_ABI32 && _MIPS_SZPTR == 32
+                            "lw    $25,0($29)\n"
+                            "lw    $4,4($29)\n"
+          #elif _MIPS_SIM == _MIPS_SIM_NABI32
+                            "lw    $25,0($29)\n"
+                            "lw    $4,8($29)\n"
+          #else
+                            "ld    $25,0($29)\n"
+                            "ld    $4,8($29)\n"
+          #endif
+                            "jalr  $25\n"
+
+                             /* Call _exit($2)
+                              */
+                            "move  $4,$2\n"
+                            "li    $2,%3\n"
+                            "syscall\n"
+
+                           "1:\n"
+          #if _MIPS_SIM == _MIPS_SIM_ABI32 && _MIPS_SZPTR == 32
+                             "addu  $29, 24\n"
+          #elif _MIPS_SIM == _MIPS_SIM_NABI32
+                             "add   $29, 16\n"
+          #else
+                             "daddu $29,16\n"
+          #endif
+                             : "+r" (__v0), "+r" (__r7)
+                             : "i"(__NR_clone), "i"(__NR_exit), "r"(fn),
+                               "r"(__stack), "r"(__flags), "r"(arg),
+                               "r"(__ptid), "r"(__ctid)
+                             : "$9", "$10", "$11", "$12", "$13", "$14", "$15",
+                               "$24", "$25", "memory");
+      }
+      LSS_RETURN(int, __v0, __r7);
+    }
+  #elif defined (__PPC__)
+    #undef  LSS_LOADARGS_0
+    #define LSS_LOADARGS_0(name, dummy...)                                    \
+        __sc_0 = __NR_##name
+    #undef  LSS_LOADARGS_1
+    #define LSS_LOADARGS_1(name, arg1)                                        \
+            LSS_LOADARGS_0(name);                                             \
+            __sc_3 = (unsigned long) (arg1)
+    #undef  LSS_LOADARGS_2
+    #define LSS_LOADARGS_2(name, arg1, arg2)                                  \
+            LSS_LOADARGS_1(name, arg1);                                       \
+            __sc_4 = (unsigned long) (arg2)
+    #undef  LSS_LOADARGS_3
+    #define LSS_LOADARGS_3(name, arg1, arg2, arg3)                            \
+            LSS_LOADARGS_2(name, arg1, arg2);                                 \
+            __sc_5 = (unsigned long) (arg3)
+    #undef  LSS_LOADARGS_4
+    #define LSS_LOADARGS_4(name, arg1, arg2, arg3, arg4)                      \
+            LSS_LOADARGS_3(name, arg1, arg2, arg3);                           \
+            __sc_6 = (unsigned long) (arg4)
+    #undef  LSS_LOADARGS_5
+    #define LSS_LOADARGS_5(name, arg1, arg2, arg3, arg4, arg5)                \
+            LSS_LOADARGS_4(name, arg1, arg2, arg3, arg4);                     \
+            __sc_7 = (unsigned long) (arg5)
+    #undef  LSS_LOADARGS_6
+    #define LSS_LOADARGS_6(name, arg1, arg2, arg3, arg4, arg5, arg6)          \
+            LSS_LOADARGS_5(name, arg1, arg2, arg3, arg4, arg5);               \
+            __sc_8 = (unsigned long) (arg6)
+    #undef  LSS_ASMINPUT_0
+    #define LSS_ASMINPUT_0 "0" (__sc_0)
+    #undef  LSS_ASMINPUT_1
+    #define LSS_ASMINPUT_1 LSS_ASMINPUT_0, "1" (__sc_3)
+    #undef  LSS_ASMINPUT_2
+    #define LSS_ASMINPUT_2 LSS_ASMINPUT_1, "2" (__sc_4)
+    #undef  LSS_ASMINPUT_3
+    #define LSS_ASMINPUT_3 LSS_ASMINPUT_2, "3" (__sc_5)
+    #undef  LSS_ASMINPUT_4
+    #define LSS_ASMINPUT_4 LSS_ASMINPUT_3, "4" (__sc_6)
+    #undef  LSS_ASMINPUT_5
+    #define LSS_ASMINPUT_5 LSS_ASMINPUT_4, "5" (__sc_7)
+    #undef  LSS_ASMINPUT_6
+    #define LSS_ASMINPUT_6 LSS_ASMINPUT_5, "6" (__sc_8)
+    #undef  LSS_BODY
+    #define LSS_BODY(nr, type, name, args...)                                 \
+        long __sc_ret, __sc_err;                                              \
+        {                                                                     \
+                        register unsigned long __sc_0 __asm__ ("r0");         \
+                        register unsigned long __sc_3 __asm__ ("r3");         \
+                        register unsigned long __sc_4 __asm__ ("r4");         \
+                        register unsigned long __sc_5 __asm__ ("r5");         \
+                        register unsigned long __sc_6 __asm__ ("r6");         \
+                        register unsigned long __sc_7 __asm__ ("r7");         \
+                        register unsigned long __sc_8 __asm__ ("r8");         \
+                                                                              \
+            LSS_LOADARGS_##nr(name, args);                                    \
+            __asm__ __volatile__                                              \
+                ("sc\n\t"                                                     \
+                 "mfcr %0"                                                    \
+                 : "=&r" (__sc_0),                                            \
+                   "=&r" (__sc_3), "=&r" (__sc_4),                            \
+                   "=&r" (__sc_5), "=&r" (__sc_6),                            \
+                   "=&r" (__sc_7), "=&r" (__sc_8)                             \
+                 : LSS_ASMINPUT_##nr                                          \
+                 : "cr0", "ctr", "memory",                                    \
+                   "r9", "r10", "r11", "r12");                                \
+            __sc_ret = __sc_3;                                                \
+            __sc_err = __sc_0;                                                \
+        }                                                                     \
+        LSS_RETURN(type, __sc_ret, __sc_err)
+    #undef _syscall0
+    #define _syscall0(type, name)                                             \
+       type LSS_NAME(name)(void) {                                            \
+          LSS_BODY(0, type, name);                                            \
+       }
+    #undef _syscall1
+    #define _syscall1(type, name, type1, arg1)                                \
+       type LSS_NAME(name)(type1 arg1) {                                      \
+          LSS_BODY(1, type, name, arg1);                                      \
+       }
+    #undef _syscall2
+    #define _syscall2(type, name, type1, arg1, type2, arg2)                   \
+       type LSS_NAME(name)(type1 arg1, type2 arg2) {                          \
+          LSS_BODY(2, type, name, arg1, arg2);                                \
+       }
+    #undef _syscall3
+    #define _syscall3(type, name, type1, arg1, type2, arg2, type3, arg3)      \
+       type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3) {              \
+          LSS_BODY(3, type, name, arg1, arg2, arg3);                          \
+       }
+    #undef _syscall4
+    #define _syscall4(type, name, type1, arg1, type2, arg2, type3, arg3,      \
+                                  type4, arg4)                                \
+       type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4) {  \
+          LSS_BODY(4, type, name, arg1, arg2, arg3, arg4);                    \
+       }
+    #undef _syscall5
+    #define _syscall5(type, name, type1, arg1, type2, arg2, type3, arg3,      \
+                                  type4, arg4, type5, arg5)                   \
+       type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4,    \
+                                               type5 arg5) {                  \
+          LSS_BODY(5, type, name, arg1, arg2, arg3, arg4, arg5);              \
+       }
+    #undef _syscall6
+    #define _syscall6(type, name, type1, arg1, type2, arg2, type3, arg3,      \
+                                  type4, arg4, type5, arg5, type6, arg6)      \
+       type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4,    \
+                                               type5 arg5, type6 arg6) {      \
+          LSS_BODY(6, type, name, arg1, arg2, arg3, arg4, arg5, arg6);        \
+       }
+    /* clone function adapted from glibc 2.3.6 clone.S                       */
+    /* TODO(csilvers): consider wrapping some args up in a struct, like we
+     * do for i386's _syscall6, so we can compile successfully on gcc 2.95
+     */
+    LSS_INLINE int LSS_NAME(clone)(int (*fn)(void *), void *child_stack,
+                                   int flags, void *arg, int *parent_tidptr,
+                                   void *newtls, int *child_tidptr) {
+      long __ret, __err;
+      {
+        register int (*__fn)(void *)    __asm__ ("r8")  = fn;
+        register void *__cstack                 __asm__ ("r4")  = child_stack;
+        register int __flags                    __asm__ ("r3")  = flags;
+        register void * __arg                   __asm__ ("r9")  = arg;
+        register int * __ptidptr                __asm__ ("r5")  = parent_tidptr;
+        register void * __newtls                __asm__ ("r6")  = newtls;
+        register int * __ctidptr                __asm__ ("r7")  = child_tidptr;
+        __asm__ __volatile__(
+            /* check for fn == NULL
+             * and child_stack == NULL
+             */
+            "cmpwi cr0, %6, 0\n\t"
+            "cmpwi cr1, %7, 0\n\t"
+            "cror cr0*4+eq, cr1*4+eq, cr0*4+eq\n\t"
+            "beq- cr0, 1f\n\t"
+
+            /* set up stack frame for child                                  */
+            "clrrwi %7, %7, 4\n\t"
+            "li 0, 0\n\t"
+            "stwu 0, -16(%7)\n\t"
+
+            /* fn, arg, child_stack are saved across the syscall: r28-30     */
+            "mr 28, %6\n\t"
+            "mr 29, %7\n\t"
+            "mr 27, %9\n\t"
+
+            /* syscall                                                       */
+            "li 0, %4\n\t"
+            /* flags already in r3
+             * child_stack already in r4
+             * ptidptr already in r5
+             * newtls already in r6
+             * ctidptr already in r7
+             */
+            "sc\n\t"
+
+            /* Test if syscall was successful                                */
+            "cmpwi cr1, 3, 0\n\t"
+            "crandc cr1*4+eq, cr1*4+eq, cr0*4+so\n\t"
+            "bne- cr1, 1f\n\t"
+
+            /* Do the function call                                          */
+            "mtctr 28\n\t"
+            "mr 3, 27\n\t"
+            "bctrl\n\t"
+
+            /* Call _exit(r3)                                                */
+            "li 0, %5\n\t"
+            "sc\n\t"
+
+            /* Return to parent                                              */
+            "1:\n"
+            "mfcr %1\n\t"
+            "mr %0, 3\n\t"
+              : "=r" (__ret), "=r" (__err)
+              : "0" (-1), "1" (EINVAL),
+                "i" (__NR_clone), "i" (__NR_exit),
+                "r" (__fn), "r" (__cstack), "r" (__flags),
+                "r" (__arg), "r" (__ptidptr), "r" (__newtls),
+                "r" (__ctidptr)
+              : "cr0", "cr1", "memory", "ctr",
+                "r0", "r29", "r27", "r28");
+      }
+      LSS_RETURN(int, __ret, __err);
+    }
+  #elif defined(__s390__)
+    #undef  LSS_REG
+    #define LSS_REG(r, a) register unsigned long __r##r __asm__("r"#r) = (unsigned long) a
+    #undef  LSS_BODY
+    #define LSS_BODY(type, name, args...)                                     \
+        register unsigned long __nr __asm__("r1")                             \
+            = (unsigned long)(__NR_##name);                                   \
+        register long __res_r2 __asm__("r2");                                 \
+        long __res;                                                           \
+        __asm__ __volatile__                                                  \
+            ("svc 0\n\t"                                                      \
+             : "=d"(__res_r2)                                                 \
+             : "d"(__nr), ## args                                             \
+             : "memory");                                                     \
+        __res = __res_r2;                                                     \
+        LSS_RETURN(type, __res)
+    #undef _syscall0
+    #define _syscall0(type, name)                                             \
+       type LSS_NAME(name)(void) {                                            \
+          LSS_BODY(type, name);                                               \
+       }
+    #undef _syscall1
+    #define _syscall1(type, name, type1, arg1)                                \
+       type LSS_NAME(name)(type1 arg1) {                                      \
+          LSS_REG(2, arg1);                                                   \
+          LSS_BODY(type, name, "0"(__r2));                                    \
+       }
+    #undef _syscall2
+    #define _syscall2(type, name, type1, arg1, type2, arg2)                   \
+       type LSS_NAME(name)(type1 arg1, type2 arg2) {                          \
+          LSS_REG(2, arg1); LSS_REG(3, arg2);                                 \
+          LSS_BODY(type, name, "0"(__r2), "d"(__r3));                         \
+       }
+    #undef _syscall3
+    #define _syscall3(type, name, type1, arg1, type2, arg2, type3, arg3)      \
+       type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3) {              \
+          LSS_REG(2, arg1); LSS_REG(3, arg2); LSS_REG(4, arg3);               \
+          LSS_BODY(type, name, "0"(__r2), "d"(__r3), "d"(__r4));              \
+       }
+    #undef _syscall4
+    #define _syscall4(type, name, type1, arg1, type2, arg2, type3, arg3,      \
+                                  type4, arg4)                                \
+       type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3,                \
+                           type4 arg4) {                                      \
+          LSS_REG(2, arg1); LSS_REG(3, arg2); LSS_REG(4, arg3);               \
+          LSS_REG(5, arg4);                                                   \
+          LSS_BODY(type, name, "0"(__r2), "d"(__r3), "d"(__r4),               \
+                               "d"(__r5));                                    \
+       }
+    #undef _syscall5
+    #define _syscall5(type, name, type1, arg1, type2, arg2, type3, arg3,      \
+                                  type4, arg4, type5, arg5)                   \
+       type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3,                \
+                           type4 arg4, type5 arg5) {                          \
+          LSS_REG(2, arg1); LSS_REG(3, arg2); LSS_REG(4, arg3);               \
+          LSS_REG(5, arg4); LSS_REG(6, arg5);                                 \
+          LSS_BODY(type, name, "0"(__r2), "d"(__r3), "d"(__r4),               \
+                               "d"(__r5), "d"(__r6));                         \
+       }
+    #undef _syscall6
+    #define _syscall6(type, name, type1, arg1, type2, arg2, type3, arg3,      \
+                                  type4, arg4, type5, arg5, type6, arg6)      \
+       type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3,                \
+                           type4 arg4, type5 arg5, type6 arg6) {              \
+          LSS_REG(2, arg1); LSS_REG(3, arg2); LSS_REG(4, arg3);               \
+          LSS_REG(5, arg4); LSS_REG(6, arg5); LSS_REG(7, arg6);               \
+          LSS_BODY(type, name, "0"(__r2), "d"(__r3), "d"(__r4),               \
+                               "d"(__r5), "d"(__r6), "d"(__r7));              \
+       }
+    LSS_INLINE int LSS_NAME(clone)(int (*fn)(void *), void *child_stack,
+                                   int flags, void *arg, int *parent_tidptr,
+                                   void *newtls, int *child_tidptr) {
+      long __ret;
+      {
+        register int  (*__fn)(void *)    __asm__ ("r1")  = fn;
+        register void  *__cstack         __asm__ ("r2")  = child_stack;
+        register int    __flags          __asm__ ("r3")  = flags;
+        register void  *__arg            __asm__ ("r0")  = arg;
+        register int   *__ptidptr        __asm__ ("r4")  = parent_tidptr;
+        register void  *__newtls         __asm__ ("r6")  = newtls;
+        register int   *__ctidptr        __asm__ ("r5")  = child_tidptr;
+        __asm__ __volatile__ (
+    #ifndef __s390x__
+                                  /* arg already in r0 */
+          "ltr %4, %4\n\t"        /* check fn, which is already in r1 */
+          "jz 1f\n\t"             /* NULL function pointer, return -EINVAL */
+          "ltr %5, %5\n\t"        /* check child_stack, which is already in r2 */
+          "jz 1f\n\t"             /* NULL stack pointer, return -EINVAL */
+                                  /* flags already in r3 */
+                                  /* parent_tidptr already in r4 */
+                                  /* child_tidptr already in r5 */
+                                  /* newtls already in r6 */
+          "svc %2\n\t"            /* invoke clone syscall */
+          "ltr %0,%%r2\n\t"       /* load return code into __ret and test */
+          "jnz 1f\n\t"            /* return to parent if non-zero */
+                                  /* start child thread */
+          "lr %%r2, %7\n\t"       /* set first parameter to void *arg */
+          "ahi %%r15, -96\n\t"    /* make room on the stack for the save area */
+          "xc 0(4,%%r15), 0(%%r15)\n\t"
+          "basr %%r14, %4\n\t"    /* jump to fn */
+          "svc %3\n"              /* invoke exit syscall */
+          "1:\n"
+    #else
+                                  /* arg already in r0 */
+          "ltgr %4, %4\n\t"       /* check fn, which is already in r1 */
+          "jz 1f\n\t"             /* NULL function pointer, return -EINVAL */
+          "ltgr %5, %5\n\t"       /* check child_stack, which is already in r2 */
+          "jz 1f\n\t"             /* NULL stack pointer, return -EINVAL */
+                                  /* flags already in r3 */
+                                  /* parent_tidptr already in r4 */
+                                  /* child_tidptr already in r5 */
+                                  /* newtls already in r6 */
+          "svc %2\n\t"            /* invoke clone syscall */
+          "ltgr %0, %%r2\n\t"     /* load return code into __ret and test */
+          "jnz 1f\n\t"            /* return to parent if non-zero */
+                                  /* start child thread */
+          "lgr %%r2, %7\n\t"      /* set first parameter to void *arg */
+          "aghi %%r15, -160\n\t"  /* make room on the stack for the save area */
+          "xc 0(8,%%r15), 0(%%r15)\n\t"
+          "basr %%r14, %4\n\t"    /* jump to fn */
+          "svc %3\n"              /* invoke exit syscall */
+          "1:\n"
+    #endif
+          : "=r" (__ret)
+          : "0" (-EINVAL), "i" (__NR_clone), "i" (__NR_exit),
+            "d" (__fn), "d" (__cstack), "d" (__flags), "d" (__arg),
+            "d" (__ptidptr), "d" (__newtls), "d" (__ctidptr)
+          : "cc", "r14", "memory"
+        );
+      }
+      LSS_RETURN(int, __ret);
+    }
+  #elif defined(__e2k__)
+
+    #undef _LSS_BODY
+    #define _LSS_BODY(nr, type, name, ...)                                    \
+      register unsigned long long __res;                                      \
+      __asm__ __volatile__                                                    \
+      (                                                                       \
+       "{\n\t"                                                                \
+       "  sdisp %%ctpr1, 0x3\n\t"                                             \
+       "  addd, s 0x0, %[sys_num], %%b[0]\n\t"                                \
+       LSS_BODY_ASM##nr                                                       \
+       "}\n\t"                                                                \
+       "{\n\t"                                                                \
+       "  call %%ctpr1, wbs = %#\n\t"                                         \
+       "}\n\t"                                                                \
+       "{\n\t"                                                                \
+       "  addd, s 0x0, %%b[0], %[res]\n\t"                                    \
+       "}\n\t"                                                                \
+       : [res] "=r" (__res)                                                   \
+       :                                                                      \
+       LSS_BODY_ARG##nr(__VA_ARGS__)                                          \
+       [sys_num] "ri" (__NR_##name)                                           \
+       : "ctpr1", "ctpr2", "ctpr3",                                           \
+       "b[0]", "b[1]", "b[2]", "b[3]",                                        \
+       "b[4]", "b[5]", "b[6]", "b[7]"                                         \
+       );                                                                     \
+       LSS_RETURN(type, __res);
+
+    #undef LSS_BODY
+    #define LSS_BODY(nr, type, name, args...) \
+      _LSS_BODY(nr, type, name, ## args)
+
+    #undef LSS_BODY_ASM0
+    #undef LSS_BODY_ASM1
+    #undef LSS_BODY_ASM2
+    #undef LSS_BODY_ASM3
+    #undef LSS_BODY_ASM4
+    #undef LSS_BODY_ASM5
+    #undef LSS_BODY_ASM6
+
+    #define LSS_BODY_ASM0
+    #define LSS_BODY_ASM1 LSS_BODY_ASM0 \
+      "  addd, s 0x0, %[arg1], %%b[1]\n\t"
+    #define LSS_BODY_ASM2 LSS_BODY_ASM1 \
+      "  addd, s 0x0, %[arg2], %%b[2]\n\t"
+    #define LSS_BODY_ASM3 LSS_BODY_ASM2 \
+      "  addd, s 0x0, %[arg3], %%b[3]\n\t"
+    #define LSS_BODY_ASM4 LSS_BODY_ASM3 \
+      "  addd, s 0x0, %[arg4], %%b[4]\n\t"
+    #define LSS_BODY_ASM5 LSS_BODY_ASM4 \
+      "  addd, s 0x0, %[arg5], %%b[5]\n\t"
+    #define LSS_BODY_ASM6 LSS_BODY_ASM5 \
+      "}\n\t" \
+      "{\n\t" \
+      "  addd, s 0x0, %[arg6], %%b[6]\n\t"
+
+    #undef LSS_SYSCALL_ARG
+    #define LSS_SYSCALL_ARG(a) ((unsigned long long)(uintptr_t)(a))
+
+    #undef LSS_BODY_ARG0
+    #undef LSS_BODY_ARG1
+    #undef LSS_BODY_ARG2
+    #undef LSS_BODY_ARG3
+    #undef LSS_BODY_ARG4
+    #undef LSS_BODY_ARG5
+    #undef LSS_BODY_ARG6
+
+    #define LSS_BODY_ARG0()
+    #define LSS_BODY_ARG1(_arg1) \
+      [arg1] "ri" LSS_SYSCALL_ARG(_arg1),
+    #define LSS_BODY_ARG2(_arg1, _arg2) \
+      LSS_BODY_ARG1(_arg1) \
+      [arg2] "ri" LSS_SYSCALL_ARG(_arg2),
+    #define LSS_BODY_ARG3(_arg1, _arg2, _arg3) \
+      LSS_BODY_ARG2(_arg1, _arg2) \
+      [arg3] "ri" LSS_SYSCALL_ARG(_arg3),
+    #define LSS_BODY_ARG4(_arg1, _arg2, _arg3, _arg4) \
+      LSS_BODY_ARG3(_arg1, _arg2, _arg3) \
+      [arg4] "ri" LSS_SYSCALL_ARG(_arg4),
+    #define LSS_BODY_ARG5(_arg1, _arg2, _arg3, _arg4, _arg5) \
+      LSS_BODY_ARG4(_arg1, _arg2, _arg3, _arg4) \
+      [arg5] "ri" LSS_SYSCALL_ARG(_arg5),
+    #define LSS_BODY_ARG6(_arg1, _arg2, _arg3, _arg4, _arg5, _arg6) \
+      LSS_BODY_ARG5(_arg1, _arg2, _arg3, _arg4, _arg5) \
+      [arg6] "ri" LSS_SYSCALL_ARG(_arg6),
+
+    #undef _syscall0
+    #define _syscall0(type, name) \
+      type LSS_NAME(name)(void) { \
+        LSS_BODY(0, type, name);     \
+      }
+
+    #undef _syscall1
+    #define _syscall1(type, name, type1, arg1) \
+      type LSS_NAME(name)(type1 arg1) { \
+         LSS_BODY(1, type, name, arg1) \
+      }
+
+    #undef _syscall2
+    #define _syscall2(type, name, type1, arg1, type2, arg2) \
+      type LSS_NAME(name)(type1 arg1, type2 arg2) { \
+          LSS_BODY(2, type, name, arg1, arg2) \
+      }
+
+    #undef _syscall3
+    #define _syscall3(type, name, type1, arg1, type2, arg2, type3, arg3) \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3) { \
+          LSS_BODY(3, type, name, arg1, arg2, arg3) \
+      }
+
+    #undef _syscall4
+    #define _syscall4(type, name, type1, arg1, type2, arg2, type3, arg3, \
+                      type4, arg4) \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4) { \
+          LSS_BODY(4, type, name, arg1, arg2, arg3, arg4) \
+      }
+
+    #undef _syscall5
+    #define _syscall5(type, name, type1, arg1, type2, arg2, type3, arg3, \
+                      type4, arg4, type5, arg5) \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4, \
+                          type5 arg5) { \
+          LSS_BODY(5, type, name, arg1, arg2, arg3, arg4, arg5) \
+      }
+
+    #undef _syscall6
+    #define _syscall6(type, name, type1, arg1, type2, arg2, type3, arg3, \
+                      type4, arg4, type5, arg5, type6, arg6) \
+      type LSS_NAME(name)(type1 arg1, type2 arg2, type3 arg3, type4 arg4, \
+                          type5 arg5, type6 arg6) { \
+          LSS_BODY(6, type, name, arg1, arg2, arg3, arg4, arg5, arg6) \
+      }
+
+    LSS_INLINE int LSS_NAME(clone)(int (*fn)(void *), void *child_stack,
+                                   int flags, void *arg, int *parent_tidptr,
+                                   void *newtls, int *child_tidptr) {
+      unsigned long long __res;
+
+      __asm__ __volatile__ (
+                            "{\n\t"
+                            "  addd,s 0x0, %[nr_clone], %%b[0]\n\t"
+                            "  addd,s 0x0, %[flags], %%db[1]\n\t"
+                            "  addd,s 0x0, %[child_stack], %%db[2]\n\t"
+                            "  addd,s 0x0, %[parent_tidptr], %%db[3]\n\t"
+                            "  addd,s 0x0, %[child_tidptr], %%db[4]\n\t"
+                            "  addd,s 0x0, %[newtls], %%db[5]\n\t"
+                            "}\n\t"
+                            /* if (fn == NULL)
+                             *   return -EINVAL;
+                             */
+
+                            "{\n\t"
+                            "  disp %%ctpr1, .L1\n\t"
+                            "}\n\t"
+                            "{\n\t"
+                            "  cmpesb,s 0x0, %[fn], %%pred0\n\t"
+                            "}\n\t"
+                            "{\n\t"
+                            "  ct %%ctpr1 ? %%pred0\n\t"
+                            "}\n\t"
+
+                            /* if (child_stack == NULL)
+                             *   return -EINVAL;
+                             */
+                            "{\n\t"
+                            "  cmpesb,s 0x0, %%db[2], %%pred0\n\t"
+                            "}\n\t"
+                            "{\n\t"
+                            "  ct %%ctpr1 ? %%pred0\n\t"
+                            "}\n\t"
+
+                            /* b[0] = syscall(%b[0] = __NR_clone,
+                             *                %db[1] = flags,
+                             *                %db[2] = child_stack,
+                             *                %db[3] = parent_tidptr,
+                             *                %db[4] = child_tidptr,
+                             *                %db[5] = newtls)
+                             */
+                            "{\n\t"
+                            "  sdisp %%ctpr1, 0x3\n\t"
+                            "}\n\t"
+                            "{\n\t"
+                            "  call %%ctpr1, wbs = %#\n\t"
+                            "}\n\t"
+
+                            /* if (%[b0] != 0)
+                             *   return %b[0];
+                             */
+                            "{\n\t"
+                            "  disp %%ctpr1, .L2\n\t"
+                            "  cmpesb,s 0x0, %%b[0], %%pred0\n\t"
+                            "}\n\t"
+                            "{\n\t"
+                            "  ct %%ctpr1 ? ~%%pred0\n\t"
+                            "}\n\t"
+                            /* In the child, now. Call "fn(arg)".
+                             */
+
+                            "{\n\t"
+                            "  movtd,s %[fn], %%ctpr1\n\t"
+                            "}\n\t"
+                            "{\n\t"
+                            "  addd,s 0x0, %[arg], %%db[0]\n\t"
+                            "}\n\t"
+                            "{\n\t"
+                            "  call %%ctpr1, wbs = %#\n\t"
+                            "}\n\t"
+                            /* Call _exit(%b[0]).
+                             */
+
+                            "{\n\t"
+                            "  sdisp %%ctpr1, 0x3\n\t"
+                            "  addd,s 0x0, %%b[0], %%b[1]\n\t"
+                            "}\n\t"
+                            "{\n\t"
+                            "  addd,s 0x0, %[nr_exit], %%b[0]\n\t"
+                            "}\n\t"
+                            "{\n\t"
+                            "  call %%ctpr1, wbs = %#\n\t"
+                            "}\n\t"
+                            "{\n\t"
+                            "  disp %%ctpr1, .L2\n\t"
+                            "  adds,s 0x0, 0x0, %%b[0]\n\t"
+                            "}\n\t"
+                            "{\n\t"
+                            "  ct %%ctpr1\n\t"
+                            "}\n\t"
+                            ".L1:\n\t"
+                            "{\n\t"
+                            "  addd,s 0x0, %[einval], %%b[0]\n\t"
+                            "}\n\t"
+                            ".L2:\n\t"
+                            "{\n\t"
+                            "  addd,s 0x0, %%b[0], %[res]\n\t"
+                            "}\n\t"
+                            : [res] "=r" LSS_SYSCALL_ARG(__res)
+                            : [nr_clone] "ri" LSS_SYSCALL_ARG(__NR_clone)
+                              [arg] "ri" LSS_SYSCALL_ARG(arg)
+                              [nr_exit] "ri" LSS_SYSCALL_ARG(__NR_exit)
+                              [flags] "ri" LSS_SYSCALL_ARG(flags)
+                              [child_stack] "ri" LSS_SYSCALL_ARG(child_stack)
+                              [parent_tidptr] "ri"
+                              LSS_SYSCALL_ARG(parent_tidptr)
+                              [newtls] "ri" LSS_SYSCALL_ARG(newtls)
+                              [child_tidptr] "ri"
+                              LSS_SYSCALL_ARG(child_tidptr)
+                              [fn] "ri" LSS_SYSCALL_ARG(fn)
+                              [einval] "ri" LSS_SYSCALL_ARG(-EINVAL)
+                            : "ctpr1", "b[0]", "b[1]", "b[2]", "b[3]",
+                              "b[4]", "b[5]", "pred0");
+      LSS_RETURN(int, __res);
+    }
+
+  #endif
+  #define __NR__exit   __NR_exit
+  #define __NR__gettid __NR_gettid
+  #define __NR__mremap __NR_mremap
+  LSS_INLINE _syscall1(void *,  brk,             void *,      e)
+  LSS_INLINE _syscall1(int,     chdir,           const char *,p)
+  LSS_INLINE _syscall1(int,     close,           int,         f)
+  LSS_INLINE _syscall2(int,     clock_getres,    int,         c,
+                       struct kernel_timespec*, t)
+  LSS_INLINE _syscall2(int,     clock_gettime,   int,         c,
+                       struct kernel_timespec*, t)
+  LSS_INLINE _syscall1(int,     dup,             int,         f)
+  #if defined(__NR_dup2)
+    // dup2 is polyfilled below when not available.
+    LSS_INLINE _syscall2(int,     dup2,            int,         s,
+                         int,            d)
+  #endif
+  #if defined(__NR_dup3)
+    LSS_INLINE _syscall3(int, dup3,  int, s, int, d, int, f)
+  #endif
+  LSS_INLINE _syscall3(int,     execve,          const char*, f,
+                       const char*const*,a,const char*const*, e)
+  LSS_INLINE _syscall1(int,     _exit,           int,         e)
+  LSS_INLINE _syscall1(int,     exit_group,      int,         e)
+  LSS_INLINE _syscall3(int,     fcntl,           int,         f,
+                       int,            c, long,   a)
+  #if defined(__NR_fork)
+    // fork is polyfilled below when not available.
+    LSS_INLINE _syscall0(pid_t,   fork)
+  #endif
+  LSS_INLINE _syscall2(int,     fstat,           int,         f,
+                      struct kernel_stat*,   b)
+  LSS_INLINE _syscall2(int,     fstatfs,         int,         f,
+                      struct kernel_statfs*, b)
+  #if defined(__x86_64__)
+    /* Need to make sure off_t isn't truncated to 32-bits under x32.  */
+    LSS_INLINE int LSS_NAME(ftruncate)(int f, off_t l) {
+      LSS_BODY(2, int, ftruncate, LSS_SYSCALL_ARG(f), (uint64_t)(l));
+    }
+  #else
+    LSS_INLINE _syscall2(int, ftruncate,           int,         f,
+                         off_t,          l)
+  #endif
+  LSS_INLINE _syscall6(int,     futex,          int*,        u,
+                       int,     o,              int,         v,
+                       struct kernel_timespec*, t,
+                       int*,    u2,             int,         v2)
+  LSS_INLINE _syscall3(int,     getdents,        int,         f,
+                       struct kernel_dirent*, d, int,    c)
+  LSS_INLINE _syscall3(int,     getdents64,      int,         f,
+                      struct kernel_dirent64*, d, int,    c)
+  LSS_INLINE _syscall0(gid_t,   getegid)
+  LSS_INLINE _syscall0(uid_t,   geteuid)
+  #if defined(__NR_getpgrp)
+    LSS_INLINE _syscall0(pid_t,   getpgrp)
+  #endif
+  LSS_INLINE _syscall0(pid_t,   getpid)
+  LSS_INLINE _syscall0(pid_t,   getppid)
+  LSS_INLINE _syscall2(int,     getpriority,     int,         a,
+                       int,            b)
+  LSS_INLINE _syscall3(int,     getresgid,       gid_t *,     r,
+                       gid_t *,         e,       gid_t *,     s)
+  LSS_INLINE _syscall3(int,     getresuid,       uid_t *,     r,
+                       uid_t *,         e,       uid_t *,     s)
+#if !defined(__ARM_EABI__)
+  LSS_INLINE _syscall2(int,     getrlimit,       int,         r,
+                      struct kernel_rlimit*, l)
+#endif
+  LSS_INLINE _syscall1(pid_t,   getsid,          pid_t,       p)
+  LSS_INLINE _syscall0(pid_t,   _gettid)
+  LSS_INLINE _syscall2(pid_t,   gettimeofday,    struct kernel_timeval*, t,
+                       void*, tz)
+  LSS_INLINE _syscall5(int,     setxattr,        const char *,p,
+                       const char *,   n,        const void *,v,
+                       size_t,         s,        int,         f)
+  LSS_INLINE _syscall5(int,     lsetxattr,       const char *,p,
+                       const char *,   n,        const void *,v,
+                       size_t,         s,        int,         f)
+  LSS_INLINE _syscall4(ssize_t, getxattr,        const char *,p,
+                       const char *,   n,        void *,      v, size_t, s)
+  LSS_INLINE _syscall4(ssize_t, lgetxattr,       const char *,p,
+                       const char *,   n,        void *,      v, size_t, s)
+  LSS_INLINE _syscall3(ssize_t, listxattr,       const char *,p,
+                       char *,   l,              size_t,      s)
+  LSS_INLINE _syscall3(ssize_t, llistxattr,      const char *,p,
+                       char *,   l,              size_t,      s)
+  LSS_INLINE _syscall3(int,     ioctl,           int,         d,
+                       int,     r,               void *,      a)
+  LSS_INLINE _syscall2(int,     ioprio_get,      int,         which,
+                       int,     who)
+  LSS_INLINE _syscall3(int,     ioprio_set,      int,         which,
+                       int,     who,             int,         ioprio)
+  LSS_INLINE _syscall2(int,     kill,            pid_t,       p,
+                       int,            s)
+  #if defined(__x86_64__)
+    /* Need to make sure off_t isn't truncated to 32-bits under x32.  */
+    LSS_INLINE off_t LSS_NAME(lseek)(int f, off_t o, int w) {
+      _LSS_BODY(3, off_t, lseek, off_t, LSS_SYSCALL_ARG(f), (uint64_t)(o),
+                                        LSS_SYSCALL_ARG(w));
+    }
+  #else
+    LSS_INLINE _syscall3(off_t,   lseek,           int,         f,
+                         off_t,          o, int,    w)
+  #endif
+  LSS_INLINE _syscall2(int,     munmap,          void*,       s,
+                       size_t,         l)
+  LSS_INLINE _syscall6(long,    move_pages,      pid_t,       p,
+                       unsigned long,  n, void **,g, int *,   d,
+                       int *,          s, int,    f)
+  LSS_INLINE _syscall3(int,     mprotect,        const void *,a,
+                       size_t,         l,        int,         p)
+  LSS_INLINE _syscall5(void*,   _mremap,         void*,       o,
+                       size_t,         os,       size_t,      ns,
+                       unsigned long,  f, void *, a)
+  #if defined(__NR_open)
+    // open is polyfilled below when not available.
+    LSS_INLINE _syscall3(int,     open,            const char*, p,
+                         int,            f, int,    m)
+  #endif
+  #if defined(__NR_poll)
+    // poll is polyfilled below when not available.
+    LSS_INLINE _syscall3(int,     poll,           struct kernel_pollfd*, u,
+                         unsigned int,   n, int,    t)
+  #endif
+  #if defined(__NR_ppoll)
+    LSS_INLINE _syscall5(int, ppoll, struct kernel_pollfd *, u,
+                         unsigned int, n, const struct kernel_timespec *, t,
+                         const struct kernel_sigset_t *, sigmask, size_t, s)
+  #endif
+  LSS_INLINE _syscall5(int,     prctl,           int,         option,
+                       unsigned long,  arg2,
+                       unsigned long,  arg3,
+                       unsigned long,  arg4,
+                       unsigned long,  arg5)
+  LSS_INLINE _syscall4(long,    ptrace,          int,         r,
+                       pid_t,          p, void *, a, void *, d)
+  #if defined(__NR_quotactl)
+    // Defined on x86_64 / i386 only
+    LSS_INLINE _syscall4(int,  quotactl,  int,  cmd,  const char *, special,
+                         int, id, caddr_t, addr)
+  #endif
+  LSS_INLINE _syscall3(ssize_t, read,            int,         f,
+                       void *,         b, size_t, c)
+  #if defined(__NR_readlink)
+    // readlink is polyfilled below when not available.
+    LSS_INLINE _syscall3(int,     readlink,        const char*, p,
+                         char*,          b, size_t, s)
+  #endif
+  #if defined(__NR_readlinkat)
+    LSS_INLINE _syscall4(int, readlinkat, int, d, const char *, p, char *, b,
+                         size_t, s)
+  #endif
+  LSS_INLINE _syscall4(int,     rt_sigaction,    int,         s,
+                       const struct kernel_sigaction*, a,
+                       struct kernel_sigaction*, o, size_t,   c)
+  LSS_INLINE _syscall2(int, rt_sigpending, struct kernel_sigset_t *, s,
+                       size_t,         c)
+  LSS_INLINE _syscall4(int, rt_sigprocmask,      int,         h,
+                       const struct kernel_sigset_t*,  s,
+                       struct kernel_sigset_t*,        o, size_t, c)
+  LSS_INLINE _syscall2(int, rt_sigsuspend,
+                       const struct kernel_sigset_t*, s,  size_t, c)
+  LSS_INLINE _syscall4(int, rt_sigtimedwait, const struct kernel_sigset_t*, s,
+                       siginfo_t*, i, const struct timespec*, t, size_t, c)
+  LSS_INLINE _syscall3(int,     sched_getaffinity,pid_t,      p,
+                       unsigned int,   l, unsigned long *, m)
+  LSS_INLINE _syscall3(int,     sched_setaffinity,pid_t,      p,
+                       unsigned int,   l, unsigned long *, m)
+  LSS_INLINE _syscall0(int,     sched_yield)
+  LSS_INLINE _syscall1(long,    set_tid_address, int *,       t)
+  LSS_INLINE _syscall1(int,     setfsgid,        gid_t,       g)
+  LSS_INLINE _syscall1(int,     setfsuid,        uid_t,       u)
+  LSS_INLINE _syscall1(int,     setuid,          uid_t,       u)
+  LSS_INLINE _syscall1(int,     setgid,          gid_t,       g)
+  LSS_INLINE _syscall2(int,     setpgid,         pid_t,       p,
+                       pid_t,          g)
+  LSS_INLINE _syscall3(int,     setpriority,     int,         a,
+                       int,            b, int,    p)
+  LSS_INLINE _syscall3(int,     setresgid,       gid_t,       r,
+                       gid_t,          e, gid_t,  s)
+  LSS_INLINE _syscall3(int,     setresuid,       uid_t,       r,
+                       uid_t,          e, uid_t,  s)
+  LSS_INLINE _syscall2(int,     setrlimit,       int,         r,
+                       const struct kernel_rlimit*, l)
+  LSS_INLINE _syscall0(pid_t,    setsid)
+  LSS_INLINE _syscall2(int,     sigaltstack,     const stack_t*, s,
+                       const stack_t*, o)
+  #if defined(__NR_sigreturn)
+    LSS_INLINE _syscall1(int,     sigreturn,       unsigned long, u)
+  #endif
+  #if defined(__NR_stat)
+    // stat and lstat are polyfilled below when not available.
+    LSS_INLINE _syscall2(int,     stat,            const char*, f,
+                        struct kernel_stat*,   b)
+  #endif
+  #if defined(__NR_lstat)
+    LSS_INLINE _syscall2(int,     lstat,           const char*, f,
+                         struct kernel_stat*,   b)
+  #endif
+  LSS_INLINE _syscall2(int,     statfs,          const char*, f,
+                      struct kernel_statfs*, b)
+  LSS_INLINE _syscall3(int,     tgkill,          pid_t,       p,
+                       pid_t,          t, int,            s)
+  LSS_INLINE _syscall2(int,     tkill,           pid_t,       p,
+                       int,            s)
+  #if defined(__NR_unlink)
+    // unlink is polyfilled below when not available.
+    LSS_INLINE _syscall1(int,     unlink,           const char*, f)
+  #endif
+  LSS_INLINE _syscall3(ssize_t, write,            int,        f,
+                       const void *,   b, size_t, c)
+  LSS_INLINE _syscall3(ssize_t, writev,           int,        f,
+                       const struct kernel_iovec*, v, size_t, c)
+  #if defined(__NR_getcpu)
+    LSS_INLINE _syscall3(long, getcpu, unsigned *, cpu,
+                         unsigned *, node, void *, unused)
+  #endif
+  #if defined(__x86_64__) || defined(__e2k__) ||                              \
+     (defined(__mips__) && _MIPS_SIM != _MIPS_SIM_ABI32)
+    LSS_INLINE _syscall3(int, recvmsg,            int,   s,
+                        struct kernel_msghdr*,     m, int, f)
+    LSS_INLINE _syscall3(int, sendmsg,            int,   s,
+                         const struct kernel_msghdr*, m, int, f)
+    LSS_INLINE _syscall6(int, sendto,             int,   s,
+                         const void*,             m, size_t, l,
+                         int,                     f,
+                         const struct kernel_sockaddr*, a, int, t)
+    LSS_INLINE _syscall2(int, shutdown,           int,   s,
+                         int,                     h)
+    LSS_INLINE _syscall3(int, socket,             int,   d,
+                         int,                     t, int,       p)
+    LSS_INLINE _syscall4(int, socketpair,         int,   d,
+                         int,                     t, int,       p, int*, s)
+  #endif
+  #if defined(__NR_fadvise64)
+    #if defined(__x86_64__)
+    /* Need to make sure loff_t isn't truncated to 32-bits under x32.  */
+    LSS_INLINE int LSS_NAME(fadvise64)(int fd, loff_t offset, loff_t len,
+                                       int advice) {
+      LSS_BODY(4, int, fadvise64, LSS_SYSCALL_ARG(fd), (uint64_t)(offset),
+                                  (uint64_t)(len), LSS_SYSCALL_ARG(advice));
+    }
+    #else
+    LSS_INLINE _syscall4(int, fadvise64,
+                         int, fd, loff_t, offset, loff_t, len, int, advice)
+    #endif
+  #elif defined(__i386__)
+    #define __NR__fadvise64_64 __NR_fadvise64_64
+    LSS_INLINE _syscall6(int, _fadvise64_64, int, fd,
+                         unsigned, offset_lo, unsigned, offset_hi,
+                         unsigned, len_lo, unsigned, len_hi,
+                         int, advice)
+
+    LSS_INLINE int LSS_NAME(fadvise64)(int fd, loff_t offset,
+                                       loff_t len, int advice) {
+      return LSS_NAME(_fadvise64_64)(fd,
+                                     (unsigned)offset, (unsigned)(offset >>32),
+                                     (unsigned)len, (unsigned)(len >> 32),
+                                     advice);
+    }
+
+  #elif defined(__s390__) && !defined(__s390x__)
+    #define __NR__fadvise64_64 __NR_fadvise64_64
+    struct kernel_fadvise64_64_args {
+      int fd;
+      long long offset;
+      long long len;
+      int advice;
+    };
+
+    LSS_INLINE _syscall1(int, _fadvise64_64,
+                         struct kernel_fadvise64_64_args *args)
+
+    LSS_INLINE int LSS_NAME(fadvise64)(int fd, loff_t offset,
+                                       loff_t len, int advice) {
+      struct kernel_fadvise64_64_args args = { fd, offset, len, advice };
+      return LSS_NAME(_fadvise64_64)(&args);
+    }
+  #endif
+  #if defined(__NR_fallocate)
+    #if defined(__x86_64__)
+    /* Need to make sure loff_t isn't truncated to 32-bits under x32.  */
+    LSS_INLINE int LSS_NAME(fallocate)(int f, int mode, loff_t offset,
+                                       loff_t len) {
+      LSS_BODY(4, int, fallocate, LSS_SYSCALL_ARG(f), LSS_SYSCALL_ARG(mode),
+                                  (uint64_t)(offset), (uint64_t)(len));
+    }
+    #elif (defined(__i386__) || (defined(__s390__) && !defined(__s390x__)) \
+           || defined(__ARM_ARCH_3__) || defined(__ARM_EABI__) \
+           || (defined(__mips__) && _MIPS_SIM == _MIPS_SIM_ABI32) \
+           || defined(__PPC__))
+    #define __NR__fallocate __NR_fallocate
+    LSS_INLINE _syscall6(int, _fallocate, int, fd,
+                         int, mode,
+                         unsigned, offset_lo, unsigned, offset_hi,
+                         unsigned, len_lo, unsigned, len_hi)
+
+    LSS_INLINE int LSS_NAME(fallocate)(int fd, int mode,
+                                       loff_t offset, loff_t len) {
+      union { loff_t off; unsigned w[2]; } o = { offset }, l = { len };
+      return LSS_NAME(_fallocate)(fd, mode, o.w[0], o.w[1], l.w[0], l.w[1]);
+    }
+    #else
+    LSS_INLINE _syscall4(int, fallocate,
+                         int, f, int, mode, loff_t, offset, loff_t, len)
+    #endif
+  #endif
+  #if defined(__NR_getrandom)
+    LSS_INLINE _syscall3(ssize_t, getrandom, void*, buffer, size_t, length,
+                         unsigned int, flags)
+  #endif
+  #if defined(__NR_newfstatat)
+    LSS_INLINE _syscall4(int, newfstatat,         int,   d,
+                         const char *,            p,
+                         struct kernel_stat*,     b, int, f)
+  #endif
+  #if defined(__x86_64__) || defined(__s390x__)
+    LSS_INLINE int LSS_NAME(getresgid32)(gid_t *rgid,
+                                         gid_t *egid,
+                                         gid_t *sgid) {
+      return LSS_NAME(getresgid)(rgid, egid, sgid);
+    }
+
+    LSS_INLINE int LSS_NAME(getresuid32)(uid_t *ruid,
+                                         uid_t *euid,
+                                         uid_t *suid) {
+      return LSS_NAME(getresuid)(ruid, euid, suid);
+    }
+
+    LSS_INLINE int LSS_NAME(setfsgid32)(gid_t gid) {
+      return LSS_NAME(setfsgid)(gid);
+    }
+
+    LSS_INLINE int LSS_NAME(setfsuid32)(uid_t uid) {
+      return LSS_NAME(setfsuid)(uid);
+    }
+
+    LSS_INLINE int LSS_NAME(setresgid32)(gid_t rgid, gid_t egid, gid_t sgid) {
+      return LSS_NAME(setresgid)(rgid, egid, sgid);
+    }
+
+    LSS_INLINE int LSS_NAME(setresuid32)(uid_t ruid, uid_t euid, uid_t suid) {
+      return LSS_NAME(setresuid)(ruid, euid, suid);
+    }
+
+    LSS_INLINE int LSS_NAME(sigaction)(int signum,
+                                       const struct kernel_sigaction *act,
+                                       struct kernel_sigaction *oldact) {
+      #if defined(__x86_64__)
+      /* On x86_64, the kernel requires us to always set our own
+       * SA_RESTORER in order to be able to return from a signal handler.
+       * This function must have a "magic" signature that the "gdb"
+       * (and maybe the kernel?) can recognize.
+       */
+      if (act != NULL && !(act->sa_flags & SA_RESTORER)) {
+        struct kernel_sigaction a = *act;
+        a.sa_flags   |= SA_RESTORER;
+        a.sa_restorer = LSS_NAME(restore_rt)();
+        return LSS_NAME(rt_sigaction)(signum, &a, oldact,
+                                      (KERNEL_NSIG+7)/8);
+      } else
+      #endif
+        return LSS_NAME(rt_sigaction)(signum, act, oldact,
+                                      (KERNEL_NSIG+7)/8);
+    }
+
+    LSS_INLINE int LSS_NAME(sigpending)(struct kernel_sigset_t *set) {
+      return LSS_NAME(rt_sigpending)(set, (KERNEL_NSIG+7)/8);
+    }
+
+    LSS_INLINE int LSS_NAME(sigsuspend)(const struct kernel_sigset_t *set) {
+      return LSS_NAME(rt_sigsuspend)(set, (KERNEL_NSIG+7)/8);
+    }
+  #endif
+  #if defined(__NR_rt_sigprocmask)
+    LSS_INLINE int LSS_NAME(sigprocmask)(int how,
+                                         const struct kernel_sigset_t *set,
+                                         struct kernel_sigset_t *oldset) {
+      return LSS_NAME(rt_sigprocmask)(how, set, oldset, (KERNEL_NSIG+7)/8);
+    }
+  #endif
+  #if defined(__NR_rt_sigtimedwait)
+    LSS_INLINE int LSS_NAME(sigtimedwait)(const struct kernel_sigset_t *set,
+                                          siginfo_t *info,
+                                          const struct timespec *timeout) {
+      return LSS_NAME(rt_sigtimedwait)(set, info, timeout, (KERNEL_NSIG+7)/8);
+    }
+  #endif
+  #if defined(__NR_wait4)
+    LSS_INLINE _syscall4(pid_t, wait4,            pid_t, p,
+                         int*,                    s, int,       o,
+                        struct kernel_rusage*,     r)
+  #endif
+  #if defined(__NR_openat)
+    LSS_INLINE _syscall4(int, openat, int, d, const char *, p, int, f, int, m)
+  #endif
+  #if defined(__NR_unlinkat)
+    LSS_INLINE _syscall3(int, unlinkat, int, d, const char *, p, int, f)
+  #endif
+  #if defined(__i386__) || defined(__ARM_ARCH_3__) || defined(__ARM_EABI__) || \
+      (defined(__s390__) && !defined(__s390x__))
+    #define __NR__getresgid32 __NR_getresgid32
+    #define __NR__getresuid32 __NR_getresuid32
+    #define __NR__setfsgid32  __NR_setfsgid32
+    #define __NR__setfsuid32  __NR_setfsuid32
+    #define __NR__setresgid32 __NR_setresgid32
+    #define __NR__setresuid32 __NR_setresuid32
+#if defined(__ARM_EABI__)
+    LSS_INLINE _syscall2(int,   ugetrlimit,        int,          r,
+                        struct kernel_rlimit*, l)
+#endif
+    LSS_INLINE _syscall3(int,     _getresgid32,    gid_t *,      r,
+                         gid_t *,            e,    gid_t *,      s)
+    LSS_INLINE _syscall3(int,     _getresuid32,    uid_t *,      r,
+                         uid_t *,            e,    uid_t *,      s)
+    LSS_INLINE _syscall1(int,     _setfsgid32,     gid_t,        f)
+    LSS_INLINE _syscall1(int,     _setfsuid32,     uid_t,        f)
+    LSS_INLINE _syscall3(int,     _setresgid32,    gid_t,        r,
+                         gid_t,              e,    gid_t,        s)
+    LSS_INLINE _syscall3(int,     _setresuid32,    uid_t,        r,
+                         uid_t,              e,    uid_t,        s)
+
+    LSS_INLINE int LSS_NAME(getresgid32)(gid_t *rgid,
+                                         gid_t *egid,
+                                         gid_t *sgid) {
+      int rc;
+      if ((rc = LSS_NAME(_getresgid32)(rgid, egid, sgid)) < 0 &&
+          LSS_ERRNO == ENOSYS) {
+        if ((rgid == NULL) || (egid == NULL) || (sgid == NULL)) {
+          return EFAULT;
+        }
+        // Clear the high bits first, since getresgid only sets 16 bits
+        *rgid = *egid = *sgid = 0;
+        rc = LSS_NAME(getresgid)(rgid, egid, sgid);
+      }
+      return rc;
+    }
+
+    LSS_INLINE int LSS_NAME(getresuid32)(uid_t *ruid,
+                                         uid_t *euid,
+                                         uid_t *suid) {
+      int rc;
+      if ((rc = LSS_NAME(_getresuid32)(ruid, euid, suid)) < 0 &&
+          LSS_ERRNO == ENOSYS) {
+        if ((ruid == NULL) || (euid == NULL) || (suid == NULL)) {
+          return EFAULT;
+        }
+        // Clear the high bits first, since getresuid only sets 16 bits
+        *ruid = *euid = *suid = 0;
+        rc = LSS_NAME(getresuid)(ruid, euid, suid);
+      }
+      return rc;
+    }
+
+    LSS_INLINE int LSS_NAME(setfsgid32)(gid_t gid) {
+      int rc;
+      if ((rc = LSS_NAME(_setfsgid32)(gid)) < 0 &&
+          LSS_ERRNO == ENOSYS) {
+        if ((unsigned int)gid & ~0xFFFFu) {
+          rc = EINVAL;
+        } else {
+          rc = LSS_NAME(setfsgid)(gid);
+        }
+      }
+      return rc;
+    }
+
+    LSS_INLINE int LSS_NAME(setfsuid32)(uid_t uid) {
+      int rc;
+      if ((rc = LSS_NAME(_setfsuid32)(uid)) < 0 &&
+          LSS_ERRNO == ENOSYS) {
+        if ((unsigned int)uid & ~0xFFFFu) {
+          rc = EINVAL;
+        } else {
+          rc = LSS_NAME(setfsuid)(uid);
+        }
+      }
+      return rc;
+    }
+
+    LSS_INLINE int LSS_NAME(setresgid32)(gid_t rgid, gid_t egid, gid_t sgid) {
+      int rc;
+      if ((rc = LSS_NAME(_setresgid32)(rgid, egid, sgid)) < 0 &&
+          LSS_ERRNO == ENOSYS) {
+        if ((unsigned int)rgid & ~0xFFFFu ||
+            (unsigned int)egid & ~0xFFFFu ||
+            (unsigned int)sgid & ~0xFFFFu) {
+          rc = EINVAL;
+        } else {
+          rc = LSS_NAME(setresgid)(rgid, egid, sgid);
+        }
+      }
+      return rc;
+    }
+
+    LSS_INLINE int LSS_NAME(setresuid32)(uid_t ruid, uid_t euid, uid_t suid) {
+      int rc;
+      if ((rc = LSS_NAME(_setresuid32)(ruid, euid, suid)) < 0 &&
+          LSS_ERRNO == ENOSYS) {
+        if ((unsigned int)ruid & ~0xFFFFu ||
+            (unsigned int)euid & ~0xFFFFu ||
+            (unsigned int)suid & ~0xFFFFu) {
+          rc = EINVAL;
+        } else {
+          rc = LSS_NAME(setresuid)(ruid, euid, suid);
+        }
+      }
+      return rc;
+    }
+  #endif
+  LSS_INLINE int LSS_NAME(sigemptyset)(struct kernel_sigset_t *set) {
+    memset(&set->sig, 0, sizeof(set->sig));
+    return 0;
+  }
+
+  LSS_INLINE int LSS_NAME(sigfillset)(struct kernel_sigset_t *set) {
+    memset(&set->sig, -1, sizeof(set->sig));
+    return 0;
+  }
+
+  LSS_INLINE int LSS_NAME(sigaddset)(struct kernel_sigset_t *set,
+                                     int signum) {
+    if (signum < 1 || signum > (int)(8*sizeof(set->sig))) {
+      LSS_ERRNO = EINVAL;
+      return -1;
+    } else {
+      set->sig[(signum - 1)/(8*sizeof(set->sig[0]))]
+          |= 1UL << ((signum - 1) % (8*sizeof(set->sig[0])));
+      return 0;
+    }
+  }
+
+  LSS_INLINE int LSS_NAME(sigdelset)(struct kernel_sigset_t *set,
+                                        int signum) {
+    if (signum < 1 || signum > (int)(8*sizeof(set->sig))) {
+      LSS_ERRNO = EINVAL;
+      return -1;
+    } else {
+      set->sig[(signum - 1)/(8*sizeof(set->sig[0]))]
+          &= ~(1UL << ((signum - 1) % (8*sizeof(set->sig[0]))));
+      return 0;
+    }
+  }
+
+  LSS_INLINE int LSS_NAME(sigismember)(struct kernel_sigset_t *set,
+                                          int signum) {
+    if (signum < 1 || signum > (int)(8*sizeof(set->sig))) {
+      LSS_ERRNO = EINVAL;
+      return -1;
+    } else {
+      return !!(set->sig[(signum - 1)/(8*sizeof(set->sig[0]))] &
+                (1UL << ((signum - 1) % (8*sizeof(set->sig[0])))));
+    }
+  }
+  #if defined(__i386__) ||                                                    \
+      defined(__ARM_ARCH_3__) || defined(__ARM_EABI__) ||                     \
+     (defined(__mips__) && _MIPS_SIM == _MIPS_SIM_ABI32) ||                   \
+      defined(__PPC__) ||                                                     \
+     (defined(__s390__) && !defined(__s390x__)) || defined(__e2k__)
+    #define __NR__sigaction   __NR_sigaction
+    #define __NR__sigpending  __NR_sigpending
+    #define __NR__sigsuspend  __NR_sigsuspend
+    #define __NR__socketcall  __NR_socketcall
+    LSS_INLINE _syscall2(int, fstat64,             int, f,
+                         struct kernel_stat64 *, b)
+    LSS_INLINE _syscall5(int, _llseek,     uint, fd,
+                         unsigned long, hi, unsigned long, lo,
+                         loff_t *, res, uint, wh)
+#if defined(__s390__) && !defined(__s390x__)
+    /* On s390, mmap2() arguments are passed in memory. */
+    LSS_INLINE void* LSS_NAME(_mmap2)(void *s, size_t l, int p, int f, int d,
+                                      off_t o) {
+      unsigned long buf[6] = { (unsigned long) s, (unsigned long) l,
+                               (unsigned long) p, (unsigned long) f,
+                               (unsigned long) d, (unsigned long) o };
+      LSS_REG(2, buf);
+      LSS_BODY(void*, mmap2, "0"(__r2));
+    }
+#else
+    #define __NR__mmap2 __NR_mmap2
+    LSS_INLINE _syscall6(void*, _mmap2,            void*, s,
+                         size_t,                   l, int,               p,
+                         int,                      f, int,               d,
+                         off_t,                    o)
+#endif
+    LSS_INLINE _syscall3(int,   _sigaction,        int,   s,
+                         const struct kernel_old_sigaction*,  a,
+                         struct kernel_old_sigaction*,        o)
+    LSS_INLINE _syscall1(int,   _sigpending, unsigned long*, s)
+    #ifdef __PPC__
+    LSS_INLINE _syscall1(int, _sigsuspend,         unsigned long, s)
+    #else
+    LSS_INLINE _syscall3(int, _sigsuspend,         const void*, a,
+                         int,                      b,
+                         unsigned long,            s)
+    #endif
+    LSS_INLINE _syscall2(int, stat64,              const char *, p,
+                         struct kernel_stat64 *, b)
+
+    LSS_INLINE int LSS_NAME(sigaction)(int signum,
+                                       const struct kernel_sigaction *act,
+                                       struct kernel_sigaction *oldact) {
+      int old_errno = LSS_ERRNO;
+      int rc;
+      struct kernel_sigaction a;
+      if (act != NULL) {
+        a             = *act;
+        #ifdef __i386__
+        /* On i386, the kernel requires us to always set our own
+         * SA_RESTORER when using realtime signals. Otherwise, it does not
+         * know how to return from a signal handler. This function must have
+         * a "magic" signature that the "gdb" (and maybe the kernel?) can
+         * recognize.
+         * Apparently, a SA_RESTORER is implicitly set by the kernel, when
+         * using non-realtime signals.
+         *
+         * TODO: Test whether ARM needs a restorer
+         */
+        if (!(a.sa_flags & SA_RESTORER)) {
+          a.sa_flags   |= SA_RESTORER;
+          a.sa_restorer = (a.sa_flags & SA_SIGINFO)
+                          ? LSS_NAME(restore_rt)() : LSS_NAME(restore)();
+        }
+        #endif
+      }
+      rc = LSS_NAME(rt_sigaction)(signum, act ? &a : act, oldact,
+                                  (KERNEL_NSIG+7)/8);
+      if (rc < 0 && LSS_ERRNO == ENOSYS) {
+        struct kernel_old_sigaction oa, ooa, *ptr_a = &oa, *ptr_oa = &ooa;
+        if (!act) {
+          ptr_a            = NULL;
+        } else {
+          oa.sa_handler_   = act->sa_handler_;
+          memcpy(&oa.sa_mask, &act->sa_mask, sizeof(oa.sa_mask));
+          #ifndef __mips__
+          oa.sa_restorer   = act->sa_restorer;
+          #endif
+          oa.sa_flags      = act->sa_flags;
+        }
+        if (!oldact) {
+          ptr_oa           = NULL;
+        }
+        LSS_ERRNO = old_errno;
+        rc = LSS_NAME(_sigaction)(signum, ptr_a, ptr_oa);
+        if (rc == 0 && oldact) {
+          if (act) {
+            memcpy(oldact, act, sizeof(*act));
+          } else {
+            memset(oldact, 0, sizeof(*oldact));
+          }
+          oldact->sa_handler_    = ptr_oa->sa_handler_;
+          oldact->sa_flags       = ptr_oa->sa_flags;
+          memcpy(&oldact->sa_mask, &ptr_oa->sa_mask, sizeof(ptr_oa->sa_mask));
+          #ifndef __mips__
+          oldact->sa_restorer    = ptr_oa->sa_restorer;
+          #endif
+        }
+      }
+      return rc;
+    }
+
+    LSS_INLINE int LSS_NAME(sigpending)(struct kernel_sigset_t *set) {
+      int old_errno = LSS_ERRNO;
+      int rc = LSS_NAME(rt_sigpending)(set, (KERNEL_NSIG+7)/8);
+      if (rc < 0 && LSS_ERRNO == ENOSYS) {
+        LSS_ERRNO = old_errno;
+        LSS_NAME(sigemptyset)(set);
+        rc = LSS_NAME(_sigpending)(&set->sig[0]);
+      }
+      return rc;
+    }
+
+    LSS_INLINE int LSS_NAME(sigsuspend)(const struct kernel_sigset_t *set) {
+      int olderrno = LSS_ERRNO;
+      int rc = LSS_NAME(rt_sigsuspend)(set, (KERNEL_NSIG+7)/8);
+      if (rc < 0 && LSS_ERRNO == ENOSYS) {
+        LSS_ERRNO = olderrno;
+        rc = LSS_NAME(_sigsuspend)(
+        #ifndef __PPC__
+                                   set, 0,
+        #endif
+                                   set->sig[0]);
+      }
+      return rc;
+    }
+  #endif
+  #if defined(__i386__) ||                                                    \
+      defined(__ARM_ARCH_3__) || defined(__ARM_EABI__) ||                     \
+     (defined(__mips__) && _MIPS_SIM == _MIPS_SIM_ABI32) ||                   \
+      defined(__PPC__) ||                                                     \
+     (defined(__s390__) && !defined(__s390x__))
+    /* On these architectures, implement mmap() with mmap2(). */
+    LSS_INLINE void* LSS_NAME(mmap)(void *s, size_t l, int p, int f, int d,
+                                    int64_t o) {
+      if (o % 4096) {
+        LSS_ERRNO = EINVAL;
+        return (void *) -1;
+      }
+      return LSS_NAME(_mmap2)(s, l, p, f, d, (o / 4096));
+    }
+  #elif defined(__s390x__)
+    /* On s390x, mmap() arguments are passed in memory. */
+    LSS_INLINE void* LSS_NAME(mmap)(void *s, size_t l, int p, int f, int d,
+                                    int64_t o) {
+      unsigned long buf[6] = { (unsigned long) s, (unsigned long) l,
+                               (unsigned long) p, (unsigned long) f,
+                               (unsigned long) d, (unsigned long) o };
+      LSS_REG(2, buf);
+      LSS_BODY(void*, mmap, "0"(__r2));
+    }
+  #elif defined(__x86_64__)
+    /* Need to make sure __off64_t isn't truncated to 32-bits under x32.  */
+    LSS_INLINE void* LSS_NAME(mmap)(void *s, size_t l, int p, int f, int d,
+                                    int64_t o) {
+      LSS_BODY(6, void*, mmap, LSS_SYSCALL_ARG(s), LSS_SYSCALL_ARG(l),
+                               LSS_SYSCALL_ARG(p), LSS_SYSCALL_ARG(f),
+                               LSS_SYSCALL_ARG(d), (uint64_t)(o));
+    }
+  #else
+    /* Remaining 64-bit architectures. */
+    LSS_INLINE _syscall6(void*, mmap, void*, addr, size_t, length, int, prot,
+                         int, flags, int, fd, int64_t, offset)
+  #endif
+  #if defined(__PPC__)
+    #undef LSS_SC_LOADARGS_0
+    #define LSS_SC_LOADARGS_0(dummy...)
+    #undef LSS_SC_LOADARGS_1
+    #define LSS_SC_LOADARGS_1(arg1)                                           \
+        __sc_4  = (unsigned long) (arg1)
+    #undef LSS_SC_LOADARGS_2
+    #define LSS_SC_LOADARGS_2(arg1, arg2)                                     \
+        LSS_SC_LOADARGS_1(arg1);                                              \
+        __sc_5  = (unsigned long) (arg2)
+    #undef LSS_SC_LOADARGS_3
+    #define LSS_SC_LOADARGS_3(arg1, arg2, arg3)                               \
+        LSS_SC_LOADARGS_2(arg1, arg2);                                        \
+        __sc_6  = (unsigned long) (arg3)
+    #undef LSS_SC_LOADARGS_4
+    #define LSS_SC_LOADARGS_4(arg1, arg2, arg3, arg4)                         \
+        LSS_SC_LOADARGS_3(arg1, arg2, arg3);                                  \
+        __sc_7  = (unsigned long) (arg4)
+    #undef LSS_SC_LOADARGS_5
+    #define LSS_SC_LOADARGS_5(arg1, arg2, arg3, arg4, arg5)                   \
+        LSS_SC_LOADARGS_4(arg1, arg2, arg3, arg4);                            \
+        __sc_8  = (unsigned long) (arg5)
+    #undef LSS_SC_BODY
+    #define LSS_SC_BODY(nr, type, opt, args...)                               \
+        long __sc_ret, __sc_err;                                              \
+        {                                                                     \
+          register unsigned long __sc_0 __asm__ ("r0") = __NR_socketcall;     \
+          register unsigned long __sc_3 __asm__ ("r3") = opt;                 \
+          register unsigned long __sc_4 __asm__ ("r4");                       \
+          register unsigned long __sc_5 __asm__ ("r5");                       \
+          register unsigned long __sc_6 __asm__ ("r6");                       \
+          register unsigned long __sc_7 __asm__ ("r7");                       \
+          register unsigned long __sc_8 __asm__ ("r8");                       \
+          LSS_SC_LOADARGS_##nr(args);                                         \
+          __asm__ __volatile__                                                \
+              ("stwu 1, -48(1)\n\t"                                           \
+               "stw 4, 20(1)\n\t"                                             \
+               "stw 5, 24(1)\n\t"                                             \
+               "stw 6, 28(1)\n\t"                                             \
+               "stw 7, 32(1)\n\t"                                             \
+               "stw 8, 36(1)\n\t"                                             \
+               "addi 4, 1, 20\n\t"                                            \
+               "sc\n\t"                                                       \
+               "mfcr %0"                                                      \
+                 : "=&r" (__sc_0),                                            \
+                   "=&r" (__sc_3), "=&r" (__sc_4),                            \
+                   "=&r" (__sc_5), "=&r" (__sc_6),                            \
+                   "=&r" (__sc_7), "=&r" (__sc_8)                             \
+                 : LSS_ASMINPUT_##nr                                          \
+                 : "cr0", "ctr", "memory");                                   \
+          __sc_ret = __sc_3;                                                  \
+          __sc_err = __sc_0;                                                  \
+        }                                                                     \
+        LSS_RETURN(type, __sc_ret, __sc_err)
+
+    LSS_INLINE ssize_t LSS_NAME(recvmsg)(int s,struct kernel_msghdr *msg,
+                                         int flags){
+      LSS_SC_BODY(3, ssize_t, 17, s, msg, flags);
+    }
+
+    LSS_INLINE ssize_t LSS_NAME(sendmsg)(int s,
+                                         const struct kernel_msghdr *msg,
+                                         int flags) {
+      LSS_SC_BODY(3, ssize_t, 16, s, msg, flags);
+    }
+
+    // TODO(csilvers): why is this ifdef'ed out?
+#if 0
+    LSS_INLINE ssize_t LSS_NAME(sendto)(int s, const void *buf, size_t len,
+                                        int flags,
+                                        const struct kernel_sockaddr *to,
+                                        unsigned int tolen) {
+      LSS_BODY(6, ssize_t, 11, s, buf, len, flags, to, tolen);
+    }
+#endif
+
+    LSS_INLINE int LSS_NAME(shutdown)(int s, int how) {
+      LSS_SC_BODY(2, int, 13, s, how);
+    }
+
+    LSS_INLINE int LSS_NAME(socket)(int domain, int type, int protocol) {
+      LSS_SC_BODY(3, int, 1, domain, type, protocol);
+    }
+
+    LSS_INLINE int LSS_NAME(socketpair)(int d, int type, int protocol,
+                                        int sv[2]) {
+      LSS_SC_BODY(4, int, 8, d, type, protocol, sv);
+    }
+  #endif
+  #if defined(__ARM_EABI__) || defined (__aarch64__)
+    LSS_INLINE _syscall3(ssize_t, recvmsg, int, s, struct kernel_msghdr*, msg,
+                         int, flags)
+    LSS_INLINE _syscall3(ssize_t, sendmsg, int, s, const struct kernel_msghdr*,
+                         msg, int, flags)
+    LSS_INLINE _syscall6(ssize_t, sendto, int, s, const void*, buf, size_t,len,
+                         int, flags, const struct kernel_sockaddr*, to,
+                         unsigned int, tolen)
+    LSS_INLINE _syscall2(int, shutdown, int, s, int, how)
+    LSS_INLINE _syscall3(int, socket, int, domain, int, type, int, protocol)
+    LSS_INLINE _syscall4(int, socketpair, int, d, int, type, int, protocol,
+                         int*, sv)
+  #endif
+  #if defined(__i386__) || defined(__ARM_ARCH_3__) ||                         \
+      (defined(__mips__) && _MIPS_SIM == _MIPS_SIM_ABI32) ||                  \
+      defined(__s390__)
+    #define __NR__socketcall  __NR_socketcall
+    LSS_INLINE _syscall2(int,      _socketcall,    int,   c,
+                         va_list,                  a)
+    LSS_INLINE int LSS_NAME(socketcall)(int op, ...) {
+      int rc;
+      va_list ap;
+      va_start(ap, op);
+      rc = LSS_NAME(_socketcall)(op, ap);
+      va_end(ap);
+      return rc;
+    }
+
+    LSS_INLINE ssize_t LSS_NAME(recvmsg)(int s,struct kernel_msghdr *msg,
+                                         int flags){
+      return (ssize_t)LSS_NAME(socketcall)(17, s, msg, flags);
+    }
+
+    LSS_INLINE ssize_t LSS_NAME(sendmsg)(int s,
+                                         const struct kernel_msghdr *msg,
+                                         int flags) {
+      return (ssize_t)LSS_NAME(socketcall)(16, s, msg, flags);
+    }
+
+    LSS_INLINE ssize_t LSS_NAME(sendto)(int s, const void *buf, size_t len,
+                                        int flags,
+                                        const struct kernel_sockaddr *to,
+                                        unsigned int tolen) {
+      return (ssize_t)LSS_NAME(socketcall)(11, s, buf, len, flags, to, tolen);
+    }
+
+    LSS_INLINE int LSS_NAME(shutdown)(int s, int how) {
+      return LSS_NAME(socketcall)(13, s, how);
+    }
+
+    LSS_INLINE int LSS_NAME(socket)(int domain, int type, int protocol) {
+      return LSS_NAME(socketcall)(1, domain, type, protocol);
+    }
+
+    LSS_INLINE int LSS_NAME(socketpair)(int d, int type, int protocol,
+                                        int sv[2]) {
+      return LSS_NAME(socketcall)(8, d, type, protocol, sv);
+    }
+  #endif
+  #if defined(__NR_fstatat64)
+    LSS_INLINE _syscall4(int,   fstatat64,        int,   d,
+                         const char *,      p,
+                         struct kernel_stat64 *,   b,    int,   f)
+  #endif
+  #if defined(__NR_waitpid)
+    // waitpid is polyfilled below when not available.
+    LSS_INLINE _syscall3(pid_t, waitpid,          pid_t, p,
+                         int*,              s,    int,   o)
+  #endif
+  #if defined(__mips__)
+    /* sys_pipe() on MIPS has non-standard calling conventions, as it returns
+     * both file handles through CPU registers.
+     */
+    LSS_INLINE int LSS_NAME(pipe)(int *p) {
+      register unsigned long __v0 __asm__("$2") = __NR_pipe;
+      register unsigned long __v1 __asm__("$3");
+      register unsigned long __r7 __asm__("$7");
+      __asm__ __volatile__ ("syscall\n"
+                            : "=r"(__v0), "=r"(__v1), "=r" (__r7)
+                            : "0"(__v0)
+                            : "$8", "$9", "$10", "$11", "$12",
+                              "$13", "$14", "$15", "$24", "$25", "memory");
+      if (__r7) {
+        unsigned long __errnovalue = __v0;
+        LSS_ERRNO = __errnovalue;
+        return -1;
+      } else {
+        p[0] = __v0;
+        p[1] = __v1;
+        return 0;
+      }
+    }
+  #elif defined(__NR_pipe)
+    // pipe is polyfilled below when not available.
+    LSS_INLINE _syscall1(int,     pipe,           int *, p)
+  #endif
+  #if defined(__NR_pipe2)
+    LSS_INLINE _syscall2(int, pipe2, int *, pipefd, int, flags)
+  #endif
+  /* TODO(csilvers): see if ppc can/should support this as well              */
+  #if defined(__i386__) || defined(__ARM_ARCH_3__) ||                         \
+      defined(__ARM_EABI__) ||                                                \
+     (defined(__mips__) && _MIPS_SIM != _MIPS_SIM_ABI64) ||                   \
+     (defined(__s390__) && !defined(__s390x__))
+    #define __NR__statfs64  __NR_statfs64
+    #define __NR__fstatfs64 __NR_fstatfs64
+    LSS_INLINE _syscall3(int, _statfs64,     const char*, p,
+                         size_t, s,struct kernel_statfs64*, b)
+    LSS_INLINE _syscall3(int, _fstatfs64,          int,   f,
+                         size_t, s,struct kernel_statfs64*, b)
+    LSS_INLINE int LSS_NAME(statfs64)(const char *p,
+                                     struct kernel_statfs64 *b) {
+      return LSS_NAME(_statfs64)(p, sizeof(*b), b);
+    }
+    LSS_INLINE int LSS_NAME(fstatfs64)(int f,struct kernel_statfs64 *b) {
+      return LSS_NAME(_fstatfs64)(f, sizeof(*b), b);
+    }
+  #endif
+
+  LSS_INLINE int LSS_NAME(execv)(const char *path, const char *const argv[]) {
+    extern char **environ;
+    return LSS_NAME(execve)(path, argv, (const char *const *)environ);
+  }
+
+  LSS_INLINE pid_t LSS_NAME(gettid)(void) {
+    pid_t tid = LSS_NAME(_gettid)();
+    if (tid != -1) {
+      return tid;
+    }
+    return LSS_NAME(getpid)();
+  }
+
+  LSS_INLINE void *LSS_NAME(mremap)(void *old_address, size_t old_size,
+                                    size_t new_size, int flags, ...) {
+    va_list ap;
+    void *new_address, *rc;
+    va_start(ap, flags);
+    new_address = va_arg(ap, void *);
+    rc = LSS_NAME(_mremap)(old_address, old_size, new_size,
+                           flags, new_address);
+    va_end(ap);
+    return rc;
+  }
+
+  LSS_INLINE int LSS_NAME(ptrace_detach)(pid_t pid) {
+    /* PTRACE_DETACH can sometimes forget to wake up the tracee and it
+     * then sends job control signals to the real parent, rather than to
+     * the tracer. We reduce the risk of this happening by starting a
+     * whole new time slice, and then quickly sending a SIGCONT signal
+     * right after detaching from the tracee.
+     *
+     * We use tkill to ensure that we only issue a wakeup for the thread being
+     * detached.  Large multi threaded apps can take a long time in the kernel
+     * processing SIGCONT.
+     */
+    int rc, err;
+    LSS_NAME(sched_yield)();
+    rc = LSS_NAME(ptrace)(PTRACE_DETACH, pid, (void *)0, (void *)0);
+    err = LSS_ERRNO;
+    LSS_NAME(tkill)(pid, SIGCONT);
+    /* Old systems don't have tkill */
+    if (LSS_ERRNO == ENOSYS)
+      LSS_NAME(kill)(pid, SIGCONT);
+    LSS_ERRNO = err;
+    return rc;
+  }
+
+  LSS_INLINE int LSS_NAME(raise)(int sig) {
+    return LSS_NAME(kill)(LSS_NAME(getpid)(), sig);
+  }
+
+  LSS_INLINE int LSS_NAME(setpgrp)(void) {
+    return LSS_NAME(setpgid)(0, 0);
+  }
+
+  #if defined(__x86_64__)
+    /* Need to make sure loff_t isn't truncated to 32-bits under x32.  */
+    LSS_INLINE ssize_t LSS_NAME(pread64)(int f, void *b, size_t c, loff_t o) {
+      LSS_BODY(4, ssize_t, pread64, LSS_SYSCALL_ARG(f), LSS_SYSCALL_ARG(b),
+                                    LSS_SYSCALL_ARG(c), (uint64_t)(o));
+    }
+
+    LSS_INLINE ssize_t LSS_NAME(pwrite64)(int f, const void *b, size_t c,
+                                          loff_t o) {
+      LSS_BODY(4, ssize_t, pwrite64, LSS_SYSCALL_ARG(f), LSS_SYSCALL_ARG(b),
+                                     LSS_SYSCALL_ARG(c), (uint64_t)(o));
+    }
+
+    LSS_INLINE int LSS_NAME(readahead)(int f, loff_t o, unsigned c) {
+      LSS_BODY(3, int, readahead, LSS_SYSCALL_ARG(f), (uint64_t)(o),
+                                  LSS_SYSCALL_ARG(c));
+    }
+  #elif defined(__mips__) && _MIPS_SIM == _MIPS_SIM_ABI64
+    LSS_INLINE _syscall4(ssize_t, pread64,        int,         f,
+                         void *,         b, size_t,   c,
+                         loff_t,         o)
+    LSS_INLINE _syscall4(ssize_t, pwrite64,       int,         f,
+                         const void *,   b, size_t,   c,
+                         loff_t,         o)
+    LSS_INLINE _syscall3(int,     readahead,      int,         f,
+                         loff_t,         o, unsigned, c)
+  #else
+    #define __NR__pread64   __NR_pread64
+    #define __NR__pwrite64  __NR_pwrite64
+    #define __NR__readahead __NR_readahead
+    #if defined(__ARM_EABI__) || defined(__mips__)
+      /* On ARM and MIPS, a 64-bit parameter has to be in an even-odd register
+       * pair. Hence these calls ignore their fourth argument (r3) so that their
+       * fifth and sixth make such a pair (r4,r5).
+       */
+      #define LSS_LLARG_PAD 0,
+      LSS_INLINE _syscall6(ssize_t, _pread64,        int,         f,
+                           void *,         b, size_t, c,
+                           unsigned, skip, unsigned, o1, unsigned, o2)
+      LSS_INLINE _syscall6(ssize_t, _pwrite64,       int,         f,
+                           const void *,   b, size_t, c,
+                           unsigned, skip, unsigned, o1, unsigned, o2)
+      LSS_INLINE _syscall5(int, _readahead,          int,         f,
+                           unsigned,     skip,
+                           unsigned,       o1, unsigned, o2, size_t, c)
+    #else
+      #define LSS_LLARG_PAD
+      LSS_INLINE _syscall5(ssize_t, _pread64,        int,         f,
+                           void *,         b, size_t, c, unsigned, o1,
+                           unsigned, o2)
+      LSS_INLINE _syscall5(ssize_t, _pwrite64,       int,         f,
+                           const void *,   b, size_t, c, unsigned, o1,
+                           long, o2)
+      LSS_INLINE _syscall4(int, _readahead,          int,         f,
+                           unsigned,       o1, unsigned, o2, size_t, c)
+    #endif
+    /* We force 64bit-wide parameters onto the stack, then access each
+     * 32-bit component individually. This guarantees that we build the
+     * correct parameters independent of the native byte-order of the
+     * underlying architecture.
+     */
+    LSS_INLINE ssize_t LSS_NAME(pread64)(int fd, void *buf, size_t count,
+                                         loff_t off) {
+      union { loff_t off; unsigned arg[2]; } o = { off };
+      return LSS_NAME(_pread64)(fd, buf, count,
+                                LSS_LLARG_PAD o.arg[0], o.arg[1]);
+    }
+    LSS_INLINE ssize_t LSS_NAME(pwrite64)(int fd, const void *buf,
+                                          size_t count, loff_t off) {
+      union { loff_t off; unsigned arg[2]; } o = { off };
+      return LSS_NAME(_pwrite64)(fd, buf, count,
+                                 LSS_LLARG_PAD o.arg[0], o.arg[1]);
+    }
+    LSS_INLINE int LSS_NAME(readahead)(int fd, loff_t off, int len) {
+      union { loff_t off; unsigned arg[2]; } o = { off };
+      return LSS_NAME(_readahead)(fd, LSS_LLARG_PAD o.arg[0], o.arg[1], len);
+    }
+  #endif
+#endif
+
+/*
+ * Polyfills for deprecated syscalls.
+ */
+
+#if !defined(__NR_dup2)
+  LSS_INLINE int LSS_NAME(dup2)(int s, int d) {
+    return LSS_NAME(dup3)(s, d, 0);
+  }
+#endif
+
+#if !defined(__NR_open)
+  LSS_INLINE int LSS_NAME(open)(const char *pathname, int flags, int mode) {
+    return LSS_NAME(openat)(AT_FDCWD, pathname, flags, mode);
+  }
+#endif
+
+#if !defined(__NR_unlink)
+  LSS_INLINE int LSS_NAME(unlink)(const char *pathname) {
+    return LSS_NAME(unlinkat)(AT_FDCWD, pathname, 0);
+  }
+#endif
+
+#if !defined(__NR_readlink)
+  LSS_INLINE int LSS_NAME(readlink)(const char *pathname, char *buffer,
+                                    size_t size) {
+    return LSS_NAME(readlinkat)(AT_FDCWD, pathname, buffer, size);
+  }
+#endif
+
+#if !defined(__NR_pipe)
+  LSS_INLINE int LSS_NAME(pipe)(int *pipefd) {
+    return LSS_NAME(pipe2)(pipefd, 0);
+  }
+#endif
+
+#if !defined(__NR_poll)
+  LSS_INLINE int LSS_NAME(poll)(struct kernel_pollfd *fds, unsigned int nfds,
+                                int timeout) {
+   struct kernel_timespec timeout_ts;
+   struct kernel_timespec *timeout_ts_p = NULL;
+
+    if (timeout >= 0) {
+      timeout_ts.tv_sec = timeout / 1000;
+      timeout_ts.tv_nsec = (timeout % 1000) * 1000000;
+      timeout_ts_p = &timeout_ts;
+    }
+    return LSS_NAME(ppoll)(fds, nfds, timeout_ts_p, NULL, 0);
+  }
+#endif
+
+#if !defined(__NR_stat)
+  LSS_INLINE int LSS_NAME(stat)(const char *pathname,
+                                struct kernel_stat *buf) {
+    return LSS_NAME(newfstatat)(AT_FDCWD, pathname, buf, 0);
+  }
+#endif
+#if !defined(__NR_lstat)
+  LSS_INLINE int LSS_NAME(lstat)(const char *pathname,
+                                 struct kernel_stat *buf) {
+    return LSS_NAME(newfstatat)(AT_FDCWD, pathname, buf, AT_SYMLINK_NOFOLLOW);
+  }
+#endif
+
+#if !defined(__NR_waitpid)
+  LSS_INLINE pid_t LSS_NAME(waitpid)(pid_t pid, int *status, int options) {
+    return LSS_NAME(wait4)(pid, status, options, 0);
+  }
+#endif
+
+#if !defined(__NR_fork)
+// TODO: define this in an arch-independant way instead of inlining the clone
+//       syscall body.
+
+# if defined(__aarch64__)
+  LSS_INLINE pid_t LSS_NAME(fork)(void) {
+    // No fork syscall on aarch64 - implement by means of the clone syscall.
+    // Note that this does not reset glibc's cached view of the PID/TID, so
+    // some glibc interfaces might go wrong in the forked subprocess.
+    int flags = SIGCHLD;
+    void *child_stack = NULL;
+    void *parent_tidptr = NULL;
+    void *newtls = NULL;
+    void *child_tidptr = NULL;
+
+    LSS_REG(0, flags);
+    LSS_REG(1, child_stack);
+    LSS_REG(2, parent_tidptr);
+    LSS_REG(3, newtls);
+    LSS_REG(4, child_tidptr);
+    LSS_BODY(pid_t, clone, "r"(__r0), "r"(__r1), "r"(__r2), "r"(__r3),
+             "r"(__r4));
+  }
+# elif defined(__x86_64__)
+  LSS_INLINE pid_t LSS_NAME(fork)(void) {
+    // Android disallows the fork syscall on x86_64 - implement by means of the
+    // clone syscall as above for aarch64.
+    int flags = SIGCHLD;
+    void *child_stack = NULL;
+    void *parent_tidptr = NULL;
+    void *newtls = NULL;
+    void *child_tidptr = NULL;
+
+    LSS_BODY(5, pid_t, clone, LSS_SYSCALL_ARG(flags),
+             LSS_SYSCALL_ARG(child_stack), LSS_SYSCALL_ARG(parent_tidptr),
+             LSS_SYSCALL_ARG(newtls), LSS_SYSCALL_ARG(child_tidptr));
+  }
+# else
+#  error missing fork polyfill for this architecture
+# endif
+#endif
+
+/* These restore the original values of these macros saved by the
+ * corresponding #pragma push_macro near the top of this file. */
+#pragma pop_macro("stat64")
+#pragma pop_macro("fstat64")
+#pragma pop_macro("lstat64")
+#pragma pop_macro("pread64")
+#pragma pop_macro("pwrite64")
+#pragma pop_macro("getdents64")
+
+#if defined(__cplusplus) && !defined(SYS_CPLUSPLUS)
+}
+#endif
+
+#endif
+#endif
diff --git a/app/src/main/cpp/shadowhook/third_party/xdl/LICENSE b/app/src/main/cpp/shadowhook/third_party/xdl/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..c20d2c800e36fe7fd741303ea5d394f14be2d3d1
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/third_party/xdl/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020-2023 HexHacking Team
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/app/src/main/cpp/shadowhook/third_party/xdl/xdl.c b/app/src/main/cpp/shadowhook/third_party/xdl/xdl.c
new file mode 100644
index 0000000000000000000000000000000000000000..a707961bd0a6de8927e91e699ba6fa245d6e813d
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/third_party/xdl/xdl.c
@@ -0,0 +1,910 @@
+// Copyright (c) 2020-2023 HexHacking Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by caikelun on 2020-10-04.
+
+#include "xdl.h"
+
+#include <android/api-level.h>
+#include <elf.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <link.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/auxv.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "xdl_iterate.h"
+#include "xdl_linker.h"
+#include "xdl_lzma.h"
+#include "xdl_util.h"
+
+#ifndef __LP64__
+#define XDL_LIB_PATH "/system/lib"
+#else
+#define XDL_LIB_PATH "/system/lib64"
+#endif
+
+#define XDL_DYNSYM_IS_EXPORT_SYM(shndx) (SHN_UNDEF != (shndx))
+#define XDL_SYMTAB_IS_EXPORT_SYM(shndx) \
+  (SHN_UNDEF != (shndx) && !((shndx) >= SHN_LORESERVE && (shndx) <= SHN_HIRESERVE))
+
+extern __attribute((weak)) unsigned long int getauxval(unsigned long int);
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+
+typedef struct xdl {
+  char *pathname;
+  uintptr_t load_bias;
+  const ElfW(Phdr) *dlpi_phdr;
+  ElfW(Half) dlpi_phnum;
+
+  struct xdl *next;     // to next xdl obj for cache in xdl_addr()
+  void *linker_handle;  // hold handle returned by xdl_linker_force_dlopen()
+
+  //
+  // (1) for searching symbols from .dynsym
+  //
+
+  bool dynsym_try_load;
+  ElfW(Sym) *dynsym;   // .dynsym
+  const char *dynstr;  // .dynstr
+
+  // .hash (SYSV hash for .dynstr)
+  struct {
+    const uint32_t *buckets;
+    uint32_t buckets_cnt;
+    const uint32_t *chains;
+    uint32_t chains_cnt;
+  } sysv_hash;
+
+  // .gnu.hash (GNU hash for .dynstr)
+  struct {
+    const uint32_t *buckets;
+    uint32_t buckets_cnt;
+    const uint32_t *chains;
+    uint32_t symoffset;
+    const ElfW(Addr) *bloom;
+    uint32_t bloom_cnt;
+    uint32_t bloom_shift;
+  } gnu_hash;
+
+  //
+  // (2) for searching symbols from .symtab
+  //
+
+  bool symtab_try_load;
+  uintptr_t base;
+
+  ElfW(Sym) *symtab;  // .symtab
+  size_t symtab_cnt;
+  char *strtab;  // .strtab
+  size_t strtab_sz;
+} xdl_t;
+
+#pragma clang diagnostic pop
+
+// load from memory
+static int xdl_dynsym_load(xdl_t *self) {
+  // find the dynamic segment
+  ElfW(Dyn) *dynamic = NULL;
+  for (size_t i = 0; i < self->dlpi_phnum; i++) {
+    const ElfW(Phdr) *phdr = &(self->dlpi_phdr[i]);
+    if (PT_DYNAMIC == phdr->p_type) {
+      dynamic = (ElfW(Dyn) *)(self->load_bias + phdr->p_vaddr);
+      break;
+    }
+  }
+  if (NULL == dynamic) return -1;
+
+  // iterate the dynamic segment
+  for (ElfW(Dyn) *entry = dynamic; entry && entry->d_tag != DT_NULL; entry++) {
+    switch (entry->d_tag) {
+      case DT_SYMTAB:  //.dynsym
+        self->dynsym = (ElfW(Sym) *)(self->load_bias + entry->d_un.d_ptr);
+        break;
+      case DT_STRTAB:  //.dynstr
+        self->dynstr = (const char *)(self->load_bias + entry->d_un.d_ptr);
+        break;
+      case DT_HASH:  //.hash
+        self->sysv_hash.buckets_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[0];
+        self->sysv_hash.chains_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[1];
+        self->sysv_hash.buckets = &(((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[2]);
+        self->sysv_hash.chains = &(self->sysv_hash.buckets[self->sysv_hash.buckets_cnt]);
+        break;
+      case DT_GNU_HASH:  //.gnu.hash
+        self->gnu_hash.buckets_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[0];
+        self->gnu_hash.symoffset = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[1];
+        self->gnu_hash.bloom_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[2];
+        self->gnu_hash.bloom_shift = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[3];
+        self->gnu_hash.bloom = (const ElfW(Addr) *)(self->load_bias + entry->d_un.d_ptr + 16);
+        self->gnu_hash.buckets = (const uint32_t *)(&(self->gnu_hash.bloom[self->gnu_hash.bloom_cnt]));
+        self->gnu_hash.chains = (const uint32_t *)(&(self->gnu_hash.buckets[self->gnu_hash.buckets_cnt]));
+        break;
+      default:
+        break;
+    }
+  }
+
+  if (NULL == self->dynsym || NULL == self->dynstr ||
+      (0 == self->sysv_hash.buckets_cnt && 0 == self->gnu_hash.buckets_cnt)) {
+    self->dynsym = NULL;
+    self->dynstr = NULL;
+    self->sysv_hash.buckets_cnt = 0;
+    self->gnu_hash.buckets_cnt = 0;
+    return -1;
+  }
+
+  return 0;
+}
+
+static void *xdl_read_file_to_heap(int file_fd, size_t file_sz, size_t data_offset, size_t data_len) {
+  if (0 == data_len) return NULL;
+  if (data_offset >= file_sz) return NULL;
+  if (data_offset + data_len > file_sz) return NULL;
+
+  if (data_offset != (size_t)lseek(file_fd, (off_t)data_offset, SEEK_SET)) return NULL;
+
+  void *data = malloc(data_len);
+  if (NULL == data) return NULL;
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wgnu-statement-expression"
+  if ((ssize_t)data_len != XDL_UTIL_TEMP_FAILURE_RETRY(read(file_fd, data, data_len)))
+#pragma clang diagnostic pop
+  {
+    free(data);
+    return NULL;
+  }
+
+  return data;
+}
+
+static void *xdl_read_file_to_heap_by_section(int file_fd, size_t file_sz, ElfW(Shdr) *shdr) {
+  return xdl_read_file_to_heap(file_fd, file_sz, (size_t)shdr->sh_offset, shdr->sh_size);
+}
+
+static void *xdl_read_memory_to_heap(void *mem, size_t mem_sz, size_t data_offset, size_t data_len) {
+  if (0 == data_len) return NULL;
+  if (data_offset >= mem_sz) return NULL;
+  if (data_offset + data_len > mem_sz) return NULL;
+
+  void *data = malloc(data_len);
+  if (NULL == data) return NULL;
+
+  memcpy(data, (void *)((uintptr_t)mem + data_offset), data_len);
+  return data;
+}
+
+static void *xdl_read_memory_to_heap_by_section(void *mem, size_t mem_sz, ElfW(Shdr) *shdr) {
+  return xdl_read_memory_to_heap(mem, mem_sz, (size_t)shdr->sh_offset, shdr->sh_size);
+}
+
+static void *xdl_get_memory(void *mem, size_t mem_sz, size_t data_offset, size_t data_len) {
+  if (0 == data_len) return NULL;
+  if (data_offset >= mem_sz) return NULL;
+  if (data_offset + data_len > mem_sz) return NULL;
+
+  return (void *)((uintptr_t)mem + data_offset);
+}
+
+static void *xdl_get_memory_by_section(void *mem, size_t mem_sz, ElfW(Shdr) *shdr) {
+  return xdl_get_memory(mem, mem_sz, (size_t)shdr->sh_offset, shdr->sh_size);
+}
+
+// load from disk and memory
+static int xdl_symtab_load_from_debugdata(xdl_t *self, int file_fd, size_t file_sz,
+                                          ElfW(Shdr) *shdr_debugdata) {
+  void *debugdata = NULL;
+  ElfW(Shdr) *shdrs = NULL;
+  int r = -1;
+
+  // get zipped .gnu_debugdata
+  uint8_t *debugdata_zip = (uint8_t *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdr_debugdata);
+  if (NULL == debugdata_zip) return -1;
+
+  // get unzipped .gnu_debugdata
+  size_t debugdata_sz;
+  if (0 != xdl_lzma_decompress(debugdata_zip, shdr_debugdata->sh_size, (uint8_t **)&debugdata, &debugdata_sz))
+    goto end;
+
+  // get ELF header
+  ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)debugdata;
+  if (0 == ehdr->e_shnum || ehdr->e_shentsize != sizeof(ElfW(Shdr))) goto end;
+
+  // get section headers
+  shdrs = (ElfW(Shdr) *)xdl_read_memory_to_heap(debugdata, debugdata_sz, (size_t)ehdr->e_shoff,
+                                                ehdr->e_shentsize * ehdr->e_shnum);
+  if (NULL == shdrs) goto end;
+
+  // get .shstrtab
+  if (SHN_UNDEF == ehdr->e_shstrndx || ehdr->e_shstrndx >= ehdr->e_shnum) goto end;
+  char *shstrtab = (char *)xdl_get_memory_by_section(debugdata, debugdata_sz, shdrs + ehdr->e_shstrndx);
+  if (NULL == shstrtab) goto end;
+
+  // find .symtab & .strtab
+  for (ElfW(Shdr) *shdr = shdrs; shdr < shdrs + ehdr->e_shnum; shdr++) {
+    char *shdr_name = shstrtab + shdr->sh_name;
+
+    if (SHT_SYMTAB == shdr->sh_type && 0 == strcmp(".symtab", shdr_name)) {
+      // get & check associated .strtab section
+      if (shdr->sh_link >= ehdr->e_shnum) continue;
+      ElfW(Shdr) *shdr_strtab = shdrs + shdr->sh_link;
+      if (SHT_STRTAB != shdr_strtab->sh_type) continue;
+
+      // get .symtab & .strtab
+      ElfW(Sym) *symtab = (ElfW(Sym) *)xdl_read_memory_to_heap_by_section(debugdata, debugdata_sz, shdr);
+      if (NULL == symtab) continue;
+      char *strtab = (char *)xdl_read_memory_to_heap_by_section(debugdata, debugdata_sz, shdr_strtab);
+      if (NULL == strtab) {
+        free(symtab);
+        continue;
+      }
+
+      // OK
+      self->symtab = symtab;
+      self->symtab_cnt = shdr->sh_size / shdr->sh_entsize;
+      self->strtab = strtab;
+      self->strtab_sz = shdr_strtab->sh_size;
+      r = 0;
+      break;
+    }
+  }
+
+end:
+  free(debugdata_zip);
+  if (NULL != debugdata) free(debugdata);
+  if (NULL != shdrs) free(shdrs);
+  return r;
+}
+
+// load from disk and memory
+static int xdl_symtab_load(xdl_t *self) {
+  if ('[' == self->pathname[0]) return -1;
+
+  int r = -1;
+  ElfW(Shdr) *shdrs = NULL;
+  char *shstrtab = NULL;
+
+  // get base address
+  uintptr_t vaddr_min = UINTPTR_MAX;
+  for (size_t i = 0; i < self->dlpi_phnum; i++) {
+    const ElfW(Phdr) *phdr = &(self->dlpi_phdr[i]);
+    if (PT_LOAD == phdr->p_type) {
+      if (vaddr_min > phdr->p_vaddr) vaddr_min = phdr->p_vaddr;
+    }
+  }
+  if (UINTPTR_MAX == vaddr_min) return -1;
+  self->base = self->load_bias + vaddr_min;
+
+  // open file
+  int flags = O_RDONLY | O_CLOEXEC;
+  int file_fd;
+  if ('/' == self->pathname[0]) {
+    file_fd = open(self->pathname, flags);
+  } else {
+    char full_pathname[1024];
+    // try the fast method
+    snprintf(full_pathname, sizeof(full_pathname), "%s/%s", XDL_LIB_PATH, self->pathname);
+    file_fd = open(full_pathname, flags);
+    if (file_fd < 0) {
+      // try the slow method
+      if (0 != xdl_iterate_get_full_pathname(self->base, full_pathname, sizeof(full_pathname))) return -1;
+      file_fd = open(full_pathname, flags);
+    }
+  }
+  if (file_fd < 0) return -1;
+  struct stat st;
+  if (0 != fstat(file_fd, &st)) goto end;
+  size_t file_sz = (size_t)st.st_size;
+
+  // get ELF header
+  ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)self->base;
+  if (0 == ehdr->e_shnum || ehdr->e_shentsize != sizeof(ElfW(Shdr))) goto end;
+
+  // get section headers
+  shdrs = (ElfW(Shdr) *)xdl_read_file_to_heap(file_fd, file_sz, (size_t)ehdr->e_shoff,
+                                              ehdr->e_shentsize * ehdr->e_shnum);
+  if (NULL == shdrs) goto end;
+
+  // get .shstrtab
+  if (SHN_UNDEF == ehdr->e_shstrndx || ehdr->e_shstrndx >= ehdr->e_shnum) goto end;
+  shstrtab = (char *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdrs + ehdr->e_shstrndx);
+  if (NULL == shstrtab) goto end;
+
+  // find .symtab & .strtab
+  for (ElfW(Shdr) *shdr = shdrs; shdr < shdrs + ehdr->e_shnum; shdr++) {
+    char *shdr_name = shstrtab + shdr->sh_name;
+
+    if (SHT_SYMTAB == shdr->sh_type && 0 == strcmp(".symtab", shdr_name)) {
+      // get & check associated .strtab section
+      if (shdr->sh_link >= ehdr->e_shnum) continue;
+      ElfW(Shdr) *shdr_strtab = shdrs + shdr->sh_link;
+      if (SHT_STRTAB != shdr_strtab->sh_type) continue;
+
+      // get .symtab & .strtab
+      ElfW(Sym) *symtab = (ElfW(Sym) *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdr);
+      if (NULL == symtab) continue;
+      char *strtab = (char *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdr_strtab);
+      if (NULL == strtab) {
+        free(symtab);
+        continue;
+      }
+
+      // OK
+      self->symtab = symtab;
+      self->symtab_cnt = shdr->sh_size / shdr->sh_entsize;
+      self->strtab = strtab;
+      self->strtab_sz = shdr_strtab->sh_size;
+      r = 0;
+      break;
+    } else if (SHT_PROGBITS == shdr->sh_type && 0 == strcmp(".gnu_debugdata", shdr_name)) {
+      if (0 == xdl_symtab_load_from_debugdata(self, file_fd, file_sz, shdr)) {
+        // OK
+        r = 0;
+        break;
+      }
+    }
+  }
+
+end:
+  close(file_fd);
+  if (NULL != shdrs) free(shdrs);
+  if (NULL != shstrtab) free(shstrtab);
+  return r;
+}
+
+static xdl_t *xdl_find_from_auxv(unsigned long type, const char *pathname) {
+  if (NULL == getauxval) return NULL;  // API level < 18
+
+  uintptr_t val = (uintptr_t)getauxval(type);
+  if (0 == val) return NULL;
+
+  // get base
+  uintptr_t base = (AT_PHDR == type ? (val & (~0xffful)) : val);
+  if (0 != memcmp((void *)base, ELFMAG, SELFMAG)) return NULL;
+
+  // ELF info
+  ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base;
+  const ElfW(Phdr) *dlpi_phdr = (const ElfW(Phdr) *)(base + ehdr->e_phoff);
+  ElfW(Half) dlpi_phnum = ehdr->e_phnum;
+
+  // get bias
+  uintptr_t min_vaddr = UINTPTR_MAX;
+  for (size_t i = 0; i < dlpi_phnum; i++) {
+    const ElfW(Phdr) *phdr = &(dlpi_phdr[i]);
+    if (PT_LOAD == phdr->p_type) {
+      if (min_vaddr > phdr->p_vaddr) min_vaddr = phdr->p_vaddr;
+    }
+  }
+  if (UINTPTR_MAX == min_vaddr || base < min_vaddr) return NULL;
+  uintptr_t load_bias = base - min_vaddr;
+
+  // create xDL object
+  xdl_t *self;
+  if (NULL == (self = calloc(1, sizeof(xdl_t)))) return NULL;
+  if (NULL == (self->pathname = strdup(pathname))) {
+    free(self);
+    return NULL;
+  }
+  self->load_bias = load_bias;
+  self->dlpi_phdr = dlpi_phdr;
+  self->dlpi_phnum = dlpi_phnum;
+  self->dynsym_try_load = false;
+  self->symtab_try_load = false;
+  return self;
+}
+
+static int xdl_find_iterate_cb(struct dl_phdr_info *info, size_t size, void *arg) {
+  (void)size;
+
+  uintptr_t *pkg = (uintptr_t *)arg;
+  xdl_t **self = (xdl_t **)*pkg++;
+  const char *filename = (const char *)*pkg;
+
+  // check load_bias
+  if (0 == info->dlpi_addr || NULL == info->dlpi_name) return 0;
+
+  // check pathname
+  if ('[' == filename[0]) {
+    if (0 != strcmp(info->dlpi_name, filename)) return 0;
+  } else if ('/' == filename[0]) {
+    if ('/' == info->dlpi_name[0]) {
+      if (0 != strcmp(info->dlpi_name, filename)) return 0;
+    } else {
+      if (!xdl_util_ends_with(filename, info->dlpi_name)) return 0;
+    }
+  } else {
+    if ('/' == info->dlpi_name[0]) {
+      if (!xdl_util_ends_with(info->dlpi_name, filename)) return 0;
+    } else {
+      if (0 != strcmp(info->dlpi_name, filename)) return 0;
+    }
+  }
+
+  // found the target ELF
+  if (NULL == ((*self) = calloc(1, sizeof(xdl_t)))) return 1;  // return failed
+  if (NULL == ((*self)->pathname = strdup(info->dlpi_name))) {
+    free(*self);
+    *self = NULL;
+    return 1;  // return failed
+  }
+  (*self)->load_bias = info->dlpi_addr;
+  (*self)->dlpi_phdr = info->dlpi_phdr;
+  (*self)->dlpi_phnum = info->dlpi_phnum;
+  (*self)->dynsym_try_load = false;
+  (*self)->symtab_try_load = false;
+  return 1;  // return OK
+}
+
+static xdl_t *xdl_find(const char *filename) {
+  // from auxv (linker, vDSO)
+  xdl_t *self = NULL;
+  if (xdl_util_ends_with(filename, XDL_UTIL_LINKER_BASENAME))
+    self = xdl_find_from_auxv(AT_BASE, XDL_UTIL_LINKER_PATHNAME);
+  else if (xdl_util_ends_with(filename, XDL_UTIL_VDSO_BASENAME))
+    self = xdl_find_from_auxv(AT_SYSINFO_EHDR, XDL_UTIL_VDSO_BASENAME);
+
+  // from auxv (app_process)
+  const char *basename, *pathname;
+#if (defined(__arm__) || defined(__i386__)) && __ANDROID_API__ < __ANDROID_API_L__
+  if (xdl_util_get_api_level() < __ANDROID_API_L__) {
+    basename = XDL_UTIL_APP_PROCESS_BASENAME_K;
+    pathname = XDL_UTIL_APP_PROCESS_PATHNAME_K;
+  } else
+#endif
+  {
+    basename = XDL_UTIL_APP_PROCESS_BASENAME;
+    pathname = XDL_UTIL_APP_PROCESS_PATHNAME;
+  }
+  if (xdl_util_ends_with(filename, basename)) self = xdl_find_from_auxv(AT_PHDR, pathname);
+
+  if (NULL != self) return self;
+
+  // from dl_iterate_phdr
+  uintptr_t pkg[2] = {(uintptr_t)&self, (uintptr_t)filename};
+  xdl_iterate_phdr(xdl_find_iterate_cb, pkg, XDL_DEFAULT);
+  return self;
+}
+
+static void *xdl_open_always_force(const char *filename) {
+  // always force dlopen()
+  void *linker_handle = xdl_linker_force_dlopen(filename);
+  if (NULL == linker_handle) return NULL;
+
+  // find
+  xdl_t *self = xdl_find(filename);
+  if (NULL == self)
+    dlclose(linker_handle);
+  else
+    self->linker_handle = linker_handle;
+
+  return (void *)self;
+}
+
+static void *xdl_open_try_force(const char *filename) {
+  // find
+  xdl_t *self = xdl_find(filename);
+  if (NULL != self) return (void *)self;
+
+  // try force dlopen()
+  void *linker_handle = xdl_linker_force_dlopen(filename);
+  if (NULL == linker_handle) return NULL;
+
+  // find again
+  self = xdl_find(filename);
+  if (NULL == self)
+    dlclose(linker_handle);
+  else
+    self->linker_handle = linker_handle;
+
+  return (void *)self;
+}
+
+void *xdl_open(const char *filename, int flags) {
+  if (NULL == filename) return NULL;
+
+  if (flags & XDL_ALWAYS_FORCE_LOAD)
+    return xdl_open_always_force(filename);
+  else if (flags & XDL_TRY_FORCE_LOAD)
+    return xdl_open_try_force(filename);
+  else
+    return xdl_find(filename);
+}
+
+void *xdl_close(void *handle) {
+  if (NULL == handle) return NULL;
+
+  xdl_t *self = (xdl_t *)handle;
+  if (NULL != self->pathname) free(self->pathname);
+  if (NULL != self->symtab) free(self->symtab);
+  if (NULL != self->strtab) free(self->strtab);
+
+  void *linker_handle = self->linker_handle;
+  free(self);
+  return linker_handle;
+}
+
+static uint32_t xdl_sysv_hash(const uint8_t *name) {
+  uint32_t h = 0, g;
+
+  while (*name) {
+    h = (h << 4) + *name++;
+    g = h & 0xf0000000;
+    h ^= g;
+    h ^= g >> 24;
+  }
+  return h;
+}
+
+static uint32_t xdl_gnu_hash(const uint8_t *name) {
+  uint32_t h = 5381;
+
+  while (*name) {
+    h += (h << 5) + *name++;
+  }
+  return h;
+}
+
+static ElfW(Sym) *xdl_dynsym_find_symbol_use_sysv_hash(xdl_t *self, const char *sym_name) {
+  uint32_t hash = xdl_sysv_hash((const uint8_t *)sym_name);
+
+  for (uint32_t i = self->sysv_hash.buckets[hash % self->sysv_hash.buckets_cnt]; 0 != i;
+       i = self->sysv_hash.chains[i]) {
+    ElfW(Sym) *sym = self->dynsym + i;
+    if (0 != strcmp(self->dynstr + sym->st_name, sym_name)) continue;
+    return sym;
+  }
+
+  return NULL;
+}
+
+static ElfW(Sym) *xdl_dynsym_find_symbol_use_gnu_hash(xdl_t *self, const char *sym_name) {
+  uint32_t hash = xdl_gnu_hash((const uint8_t *)sym_name);
+
+  static uint32_t elfclass_bits = sizeof(ElfW(Addr)) * 8;
+  size_t word = self->gnu_hash.bloom[(hash / elfclass_bits) % self->gnu_hash.bloom_cnt];
+  size_t mask = 0 | (size_t)1 << (hash % elfclass_bits) |
+                (size_t)1 << ((hash >> self->gnu_hash.bloom_shift) % elfclass_bits);
+
+  // if at least one bit is not set, this symbol is surely missing
+  if ((word & mask) != mask) return NULL;
+
+  // ignore STN_UNDEF
+  uint32_t i = self->gnu_hash.buckets[hash % self->gnu_hash.buckets_cnt];
+  if (i < self->gnu_hash.symoffset) return NULL;
+
+  // loop through the chain
+  while (1) {
+    ElfW(Sym) *sym = self->dynsym + i;
+    uint32_t sym_hash = self->gnu_hash.chains[i - self->gnu_hash.symoffset];
+
+    if ((hash | (uint32_t)1) == (sym_hash | (uint32_t)1)) {
+      if (0 == strcmp(self->dynstr + sym->st_name, sym_name)) {
+        return sym;
+      }
+    }
+
+    // chain ends with an element with the lowest bit set to 1
+    if (sym_hash & (uint32_t)1) break;
+
+    i++;
+  }
+
+  return NULL;
+}
+
+void *xdl_sym(void *handle, const char *symbol, size_t *symbol_size) {
+  if (NULL == handle || NULL == symbol) return NULL;
+  if (NULL != symbol_size) *symbol_size = 0;
+
+  xdl_t *self = (xdl_t *)handle;
+
+  // load .dynsym only once
+  if (!self->dynsym_try_load) {
+    self->dynsym_try_load = true;
+    if (0 != xdl_dynsym_load(self)) return NULL;
+  }
+
+  // find symbol
+  if (NULL == self->dynsym) return NULL;
+  ElfW(Sym) *sym = NULL;
+  if (self->gnu_hash.buckets_cnt > 0) {
+    // use GNU hash (.gnu.hash -> .dynsym -> .dynstr), O(x) + O(1) + O(1)
+    sym = xdl_dynsym_find_symbol_use_gnu_hash(self, symbol);
+  }
+  if (NULL == sym && self->sysv_hash.buckets_cnt > 0) {
+    // use SYSV hash (.hash -> .dynsym -> .dynstr), O(x) + O(1) + O(1)
+    sym = xdl_dynsym_find_symbol_use_sysv_hash(self, symbol);
+  }
+  if (NULL == sym || !XDL_DYNSYM_IS_EXPORT_SYM(sym->st_shndx)) return NULL;
+
+  if (NULL != symbol_size) *symbol_size = sym->st_size;
+  return (void *)(self->load_bias + sym->st_value);
+}
+
+// clang-format off
+/*
+ * For internal symbols in .symtab, LLVM may add some suffixes (for example for thinLTO).
+ * The format of the suffix is: ".xxxx.[hash]". LLVM may add multiple suffixes at once.
+ * The symbol name after removing these all suffixes is called canonical name.
+ *
+ * Because the hash part in the suffix may change when recompiled, so here we only match
+ * the canonical name.
+ *
+ * IN ADDITION: According to C/C++ syntax, it is illegal for a function name to contain
+ * dot character('.'), either in the middle or at the end.
+ *
+ * samples:
+ *
+ * symbol name in .symtab          lookup                       is match
+ * ----------------------          ----------------             --------
+ * abcd                            abc                          N
+ * abcd                            abcd                         Y
+ * abcd.llvm.10190306339727611508  abc                          N
+ * abcd.llvm.10190306339727611508  abcd                         Y
+ * abcd.llvm.10190306339727611508  abcd.                        N
+ * abcd.llvm.10190306339727611508  abcd.llvm                    Y
+ * abcd.llvm.10190306339727611508  abcd.llvm.                   N
+ * abcd.__uniq.513291356003753     abcd.__uniq.51329            N
+ * abcd.__uniq.513291356003753     abcd.__uniq.513291356003753  Y
+ */
+// clang-format on
+static inline bool xdl_dsym_is_match(const char *str, const char *sym, size_t str_len) {
+  if (__predict_false(0 == str_len)) return false;
+
+  do {
+    if (*str != *sym) return __predict_false('.' == *str && '\0' == *sym);
+    str++;
+    sym++;
+    if ('\0' == *str) break;
+  } while (0 != --str_len);
+
+  return true;
+}
+
+void *xdl_dsym(void *handle, const char *symbol, size_t *symbol_size) {
+  if (NULL == handle || NULL == symbol) return NULL;
+  if (NULL != symbol_size) *symbol_size = 0;
+
+  xdl_t *self = (xdl_t *)handle;
+
+  // load .symtab only once
+  if (!self->symtab_try_load) {
+    self->symtab_try_load = true;
+    if (0 != xdl_symtab_load(self)) return NULL;
+  }
+
+  // find symbol
+  if (NULL == self->symtab) return NULL;
+  for (size_t i = 0; i < self->symtab_cnt; i++) {
+    ElfW(Sym) *sym = self->symtab + i;
+
+    if (!XDL_SYMTAB_IS_EXPORT_SYM(sym->st_shndx)) continue;
+    // if (0 != strncmp(self->strtab + sym->st_name, symbol, self->strtab_sz - sym->st_name)) continue;
+    if (!xdl_dsym_is_match(self->strtab + sym->st_name, symbol, self->strtab_sz - sym->st_name)) continue;
+
+    if (NULL != symbol_size) *symbol_size = sym->st_size;
+    return (void *)(self->load_bias + sym->st_value);
+  }
+
+  return NULL;
+}
+
+static bool xdl_elf_is_match(uintptr_t load_bias, const ElfW(Phdr) *dlpi_phdr, ElfW(Half) dlpi_phnum,
+                             uintptr_t addr) {
+  if (addr < load_bias) return false;
+
+  uintptr_t vaddr = addr - load_bias;
+  for (size_t i = 0; i < dlpi_phnum; i++) {
+    const ElfW(Phdr) *phdr = &(dlpi_phdr[i]);
+    if (PT_LOAD != phdr->p_type) continue;
+
+    if (phdr->p_vaddr <= vaddr && vaddr < phdr->p_vaddr + phdr->p_memsz) return true;
+  }
+
+  return false;
+}
+
+static int xdl_open_by_addr_iterate_cb(struct dl_phdr_info *info, size_t size, void *arg) {
+  (void)size;
+
+  uintptr_t *pkg = (uintptr_t *)arg;
+  xdl_t **self = (xdl_t **)*pkg++;
+  uintptr_t addr = *pkg;
+
+  if (xdl_elf_is_match(info->dlpi_addr, info->dlpi_phdr, info->dlpi_phnum, addr)) {
+    // found the target ELF
+    if (NULL == ((*self) = calloc(1, sizeof(xdl_t)))) return 1;  // failed
+    if (NULL == ((*self)->pathname = strdup(info->dlpi_name))) {
+      free(*self);
+      *self = NULL;
+      return 1;  // failed
+    }
+    (*self)->load_bias = info->dlpi_addr;
+    (*self)->dlpi_phdr = info->dlpi_phdr;
+    (*self)->dlpi_phnum = info->dlpi_phnum;
+    (*self)->dynsym_try_load = false;
+    (*self)->symtab_try_load = false;
+    return 1;  // OK
+  }
+
+  return 0;  // mismatch
+}
+
+static void *xdl_open_by_addr(void *addr) {
+  if (NULL == addr) return NULL;
+
+  xdl_t *self = NULL;
+  uintptr_t pkg[2] = {(uintptr_t)&self, (uintptr_t)addr};
+  xdl_iterate_phdr(xdl_open_by_addr_iterate_cb, pkg, XDL_DEFAULT);
+
+  return (void *)self;
+}
+
+static bool xdl_sym_is_match(ElfW(Sym) *sym, uintptr_t offset, bool is_symtab) {
+  if (is_symtab) {
+    if (!XDL_SYMTAB_IS_EXPORT_SYM(sym->st_shndx)) false;
+  } else {
+    if (!XDL_DYNSYM_IS_EXPORT_SYM(sym->st_shndx)) false;
+  }
+
+  return ELF_ST_TYPE(sym->st_info) != STT_TLS && offset >= sym->st_value &&
+         offset < sym->st_value + sym->st_size;
+}
+
+static ElfW(Sym) *xdl_sym_by_addr(void *handle, void *addr) {
+  xdl_t *self = (xdl_t *)handle;
+
+  // load .dynsym only once
+  if (!self->dynsym_try_load) {
+    self->dynsym_try_load = true;
+    if (0 != xdl_dynsym_load(self)) return NULL;
+  }
+
+  // find symbol
+  if (NULL == self->dynsym) return NULL;
+  uintptr_t offset = (uintptr_t)addr - self->load_bias;
+  if (self->gnu_hash.buckets_cnt > 0) {
+    const uint32_t *chains_all = self->gnu_hash.chains - self->gnu_hash.symoffset;
+    for (size_t i = 0; i < self->gnu_hash.buckets_cnt; i++) {
+      uint32_t n = self->gnu_hash.buckets[i];
+      if (n < self->gnu_hash.symoffset) continue;
+      do {
+        ElfW(Sym) *sym = self->dynsym + n;
+        if (xdl_sym_is_match(sym, offset, false)) return sym;
+      } while ((chains_all[n++] & 1) == 0);
+    }
+  } else if (self->sysv_hash.chains_cnt > 0) {
+    for (size_t i = 0; i < self->sysv_hash.chains_cnt; i++) {
+      ElfW(Sym) *sym = self->dynsym + i;
+      if (xdl_sym_is_match(sym, offset, false)) return sym;
+    }
+  }
+
+  return NULL;
+}
+
+static ElfW(Sym) *xdl_dsym_by_addr(void *handle, void *addr) {
+  xdl_t *self = (xdl_t *)handle;
+
+  // load .symtab only once
+  if (!self->symtab_try_load) {
+    self->symtab_try_load = true;
+    if (0 != xdl_symtab_load(self)) return NULL;
+  }
+
+  // find symbol
+  if (NULL == self->symtab) return NULL;
+  uintptr_t offset = (uintptr_t)addr - self->load_bias;
+  for (size_t i = 0; i < self->symtab_cnt; i++) {
+    ElfW(Sym) *sym = self->symtab + i;
+    if (xdl_sym_is_match(sym, offset, true)) return sym;
+  }
+
+  return NULL;
+}
+
+int xdl_addr(void *addr, xdl_info_t *info, void **cache) {
+  if (NULL == addr || NULL == info || NULL == cache) return 0;
+
+  memset(info, 0, sizeof(Dl_info));
+
+  // find handle from cache
+  xdl_t *handle = NULL;
+  for (handle = *((xdl_t **)cache); NULL != handle; handle = handle->next)
+    if (xdl_elf_is_match(handle->load_bias, handle->dlpi_phdr, handle->dlpi_phnum, (uintptr_t)addr)) break;
+
+  // create new handle, save handle to cache
+  if (NULL == handle) {
+    handle = (xdl_t *)xdl_open_by_addr(addr);
+    if (NULL == handle) return 0;
+    handle->next = *(xdl_t **)cache;
+    *(xdl_t **)cache = handle;
+  }
+
+  // we have at least: load_bias, pathname, dlpi_phdr, dlpi_phnum
+  info->dli_fbase = (void *)handle->load_bias;
+  info->dli_fname = handle->pathname;
+  info->dli_sname = NULL;
+  info->dli_saddr = 0;
+  info->dli_ssize = 0;
+  info->dlpi_phdr = handle->dlpi_phdr;
+  info->dlpi_phnum = (size_t)handle->dlpi_phnum;
+
+  // keep looking for: symbol name, symbol offset, symbol size
+  ElfW(Sym) *sym;
+  if (NULL != (sym = xdl_sym_by_addr((void *)handle, addr))) {
+    info->dli_sname = handle->dynstr + sym->st_name;
+    info->dli_saddr = (void *)(handle->load_bias + sym->st_value);
+    info->dli_ssize = sym->st_size;
+  } else if (NULL != (sym = xdl_dsym_by_addr((void *)handle, addr))) {
+    info->dli_sname = handle->strtab + sym->st_name;
+    info->dli_saddr = (void *)(handle->load_bias + sym->st_value);
+    info->dli_ssize = sym->st_size;
+  }
+
+  return 1;
+}
+
+void xdl_addr_clean(void **cache) {
+  if (NULL == cache) return;
+
+  xdl_t *handle = *((xdl_t **)cache);
+  while (NULL != handle) {
+    xdl_t *tmp = handle;
+    handle = handle->next;
+    xdl_close(tmp);
+  }
+  *cache = NULL;
+}
+
+int xdl_iterate_phdr(int (*callback)(struct dl_phdr_info *, size_t, void *), void *data, int flags) {
+  if (NULL == callback) return 0;
+
+  return xdl_iterate_phdr_impl(callback, data, flags);
+}
+
+int xdl_info(void *handle, int request, void *info) {
+  if (NULL == handle || XDL_DI_DLINFO != request || NULL == info) return -1;
+
+  xdl_t *self = (xdl_t *)handle;
+  xdl_info_t *dlinfo = (xdl_info_t *)info;
+
+  dlinfo->dli_fbase = (void *)self->load_bias;
+  dlinfo->dli_fname = self->pathname;
+  dlinfo->dli_sname = NULL;
+  dlinfo->dli_saddr = 0;
+  dlinfo->dli_ssize = 0;
+  dlinfo->dlpi_phdr = self->dlpi_phdr;
+  dlinfo->dlpi_phnum = (size_t)self->dlpi_phnum;
+  return 0;
+}
diff --git a/app/src/main/cpp/shadowhook/third_party/xdl/xdl.h b/app/src/main/cpp/shadowhook/third_party/xdl/xdl.h
new file mode 100644
index 0000000000000000000000000000000000000000..d927c43ac6bfb6ecfcbf8ad496f61596fc5083e9
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/third_party/xdl/xdl.h
@@ -0,0 +1,92 @@
+// Copyright (c) 2020-2023 HexHacking Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by caikelun on 2020-10-04.
+
+//
+// xDL version: 2.1.0
+//
+// xDL is an enhanced implementation of the Android DL series functions.
+// For more information, documentation, and the latest version please check:
+// https://github.com/hexhacking/xDL
+//
+
+#ifndef IO_GITHUB_HEXHACKING_XDL
+#define IO_GITHUB_HEXHACKING_XDL
+
+#include <dlfcn.h>
+#include <link.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+  // same as Dl_info:
+  const char *dli_fname;  // Pathname of shared object that contains address.
+  void *dli_fbase;        // Address at which shared object is loaded.
+  const char *dli_sname;  // Name of nearest symbol with address lower than addr.
+  void *dli_saddr;        // Exact address of symbol named in dli_sname.
+  // added by xDL:
+  size_t dli_ssize;             // Symbol size of nearest symbol with address lower than addr.
+  const ElfW(Phdr) *dlpi_phdr;  // Pointer to array of ELF program headers for this object.
+  size_t dlpi_phnum;            // Number of items in dlpi_phdr.
+} xdl_info_t;
+
+//
+// Default value for flags in both xdl_open() and xdl_iterate_phdr().
+//
+#define XDL_DEFAULT 0x00
+
+//
+// Enhanced dlopen() / dlclose() / dlsym().
+//
+#define XDL_TRY_FORCE_LOAD    0x01
+#define XDL_ALWAYS_FORCE_LOAD 0x02
+void *xdl_open(const char *filename, int flags);
+void *xdl_close(void *handle);
+void *xdl_sym(void *handle, const char *symbol, size_t *symbol_size);
+void *xdl_dsym(void *handle, const char *symbol, size_t *symbol_size);
+
+//
+// Enhanced dladdr().
+//
+int xdl_addr(void *addr, xdl_info_t *info, void **cache);
+void xdl_addr_clean(void **cache);
+
+//
+// Enhanced dl_iterate_phdr().
+//
+#define XDL_FULL_PATHNAME 0x01
+int xdl_iterate_phdr(int (*callback)(struct dl_phdr_info *, size_t, void *), void *data, int flags);
+
+//
+// Custom dlinfo().
+//
+#define XDL_DI_DLINFO 1  // type of info: xdl_info_t
+int xdl_info(void *handle, int request, void *info);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/app/src/main/cpp/shadowhook/third_party/xdl/xdl_iterate.c b/app/src/main/cpp/shadowhook/third_party/xdl/xdl_iterate.c
new file mode 100644
index 0000000000000000000000000000000000000000..b89b642f4978f94143c898cf46248678d8477bc8
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/third_party/xdl/xdl_iterate.c
@@ -0,0 +1,297 @@
+// Copyright (c) 2020-2023 HexHacking Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by caikelun on 2020-10-04.
+
+#include "xdl_iterate.h"
+
+#include <android/api-level.h>
+#include <ctype.h>
+#include <dlfcn.h>
+#include <elf.h>
+#include <inttypes.h>
+#include <link.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/auxv.h>
+
+#include "xdl.h"
+#include "xdl_linker.h"
+#include "xdl_util.h"
+
+/*
+ * =========================================================================================================
+ * API-LEVEL  ANDROID-VERSION  SOLUTION
+ * =========================================================================================================
+ * 16         4.1              /proc/self/maps
+ * 17         4.2              /proc/self/maps
+ * 18         4.3              /proc/self/maps
+ * 19         4.4              /proc/self/maps
+ * 20         4.4W             /proc/self/maps
+ * ---------------------------------------------------------------------------------------------------------
+ * 21         5.0              dl_iterate_phdr() + __dl__ZL10g_dl_mutex + linker/linker64 from getauxval(3)
+ * 22         5.1              dl_iterate_phdr() + __dl__ZL10g_dl_mutex + linker/linker64 from getauxval(3)
+ * ---------------------------------------------------------------------------------------------------------
+ * 23         >= 6.0           dl_iterate_phdr() + linker/linker64 from getauxval(3)
+ * =========================================================================================================
+ */
+
+extern __attribute((weak)) int dl_iterate_phdr(int (*)(struct dl_phdr_info *, size_t, void *), void *);
+extern __attribute((weak)) unsigned long int getauxval(unsigned long int);
+
+static uintptr_t xdl_iterate_get_min_vaddr(struct dl_phdr_info *info) {
+  uintptr_t min_vaddr = UINTPTR_MAX;
+  for (size_t i = 0; i < info->dlpi_phnum; i++) {
+    const ElfW(Phdr) *phdr = &(info->dlpi_phdr[i]);
+    if (PT_LOAD == phdr->p_type) {
+      if (min_vaddr > phdr->p_vaddr) min_vaddr = phdr->p_vaddr;
+    }
+  }
+  return min_vaddr;
+}
+
+static int xdl_iterate_open_or_rewind_maps(FILE **maps) {
+  if (NULL == *maps) {
+    *maps = fopen("/proc/self/maps", "r");
+    if (NULL == *maps) return -1;
+  } else
+    rewind(*maps);
+
+  return 0;
+}
+
+static int xdl_iterate_get_pathname_from_maps(uintptr_t base, char *buf, size_t buf_len, FILE **maps) {
+  // open or rewind maps-file
+  if (0 != xdl_iterate_open_or_rewind_maps(maps)) return -1;  // failed
+
+  char line[1024];
+  while (fgets(line, sizeof(line), *maps)) {
+    // check base address
+    uintptr_t start, end;
+    if (2 != sscanf(line, "%" SCNxPTR "-%" SCNxPTR " r", &start, &end)) continue;
+    if (base < start) break;  // failed
+    if (base >= end) continue;
+
+    // get pathname
+    char *pathname = strchr(line, '/');
+    if (NULL == pathname) break;  // failed
+    xdl_util_trim_ending(pathname);
+
+    // found it
+    strlcpy(buf, pathname, buf_len);
+    return 0;  // OK
+  }
+
+  return -1;  // failed
+}
+
+static int xdl_iterate_by_linker_cb(struct dl_phdr_info *info, size_t size, void *arg) {
+  uintptr_t *pkg = (uintptr_t *)arg;
+  xdl_iterate_phdr_cb_t cb = (xdl_iterate_phdr_cb_t)*pkg++;
+  void *cb_arg = (void *)*pkg++;
+  FILE **maps = (FILE **)*pkg++;
+  uintptr_t linker_load_bias = *pkg++;
+  int flags = (int)*pkg;
+
+  // ignore invalid ELF
+  if (0 == info->dlpi_addr || NULL == info->dlpi_name || '\0' == info->dlpi_name[0]) return 0;
+
+  // ignore linker if we have returned it already
+  if (linker_load_bias == info->dlpi_addr) return 0;
+
+  struct dl_phdr_info info_fixed;
+  info_fixed.dlpi_addr = info->dlpi_addr;
+  info_fixed.dlpi_name = info->dlpi_name;
+  info_fixed.dlpi_phdr = info->dlpi_phdr;
+  info_fixed.dlpi_phnum = info->dlpi_phnum;
+  info = &info_fixed;
+
+  // fix dlpi_phdr & dlpi_phnum (from memory)
+  if (NULL == info->dlpi_phdr || 0 == info->dlpi_phnum) {
+    ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)info->dlpi_addr;
+    info->dlpi_phdr = (ElfW(Phdr) *)(info->dlpi_addr + ehdr->e_phoff);
+    info->dlpi_phnum = ehdr->e_phnum;
+  }
+
+  // fix dlpi_name (from /proc/self/maps)
+  if ('/' != info->dlpi_name[0] && '[' != info->dlpi_name[0] && (0 != (flags & XDL_FULL_PATHNAME))) {
+    // get base address
+    uintptr_t min_vaddr = xdl_iterate_get_min_vaddr(info);
+    if (UINTPTR_MAX == min_vaddr) return 0;  // ignore this ELF
+    uintptr_t base = (uintptr_t)(info->dlpi_addr + min_vaddr);
+
+    char buf[1024];
+    if (0 != xdl_iterate_get_pathname_from_maps(base, buf, sizeof(buf), maps)) return 0;  // ignore this ELF
+
+    info->dlpi_name = (const char *)buf;
+  }
+
+  // callback
+  return cb(info, size, cb_arg);
+}
+
+static uintptr_t xdl_iterate_get_linker_base(void) {
+  if (NULL == getauxval) return 0;
+
+  uintptr_t base = (uintptr_t)getauxval(AT_BASE);
+  if (0 == base) return 0;
+  if (0 != memcmp((void *)base, ELFMAG, SELFMAG)) return 0;
+
+  return base;
+}
+
+static int xdl_iterate_do_callback(xdl_iterate_phdr_cb_t cb, void *cb_arg, uintptr_t base,
+                                   const char *pathname, uintptr_t *load_bias) {
+  ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base;
+
+  struct dl_phdr_info info;
+  info.dlpi_name = pathname;
+  info.dlpi_phdr = (const ElfW(Phdr) *)(base + ehdr->e_phoff);
+  info.dlpi_phnum = ehdr->e_phnum;
+
+  // get load bias
+  uintptr_t min_vaddr = xdl_iterate_get_min_vaddr(&info);
+  if (UINTPTR_MAX == min_vaddr) return 0;  // ignore invalid ELF
+  info.dlpi_addr = (ElfW(Addr))(base - min_vaddr);
+  if (NULL != load_bias) *load_bias = info.dlpi_addr;
+
+  return cb(&info, sizeof(struct dl_phdr_info), cb_arg);
+}
+
+static int xdl_iterate_by_linker(xdl_iterate_phdr_cb_t cb, void *cb_arg, int flags) {
+  if (NULL == dl_iterate_phdr) return 0;
+
+  int api_level = xdl_util_get_api_level();
+  FILE *maps = NULL;
+  int r;
+
+  // dl_iterate_phdr(3) does NOT contain linker/linker64 when Android version < 8.1 (API level 27).
+  // Here we always try to get linker base address from auxv.
+  uintptr_t linker_load_bias = 0;
+  uintptr_t linker_base = xdl_iterate_get_linker_base();
+  if (0 != linker_base) {
+    if (0 !=
+        (r = xdl_iterate_do_callback(cb, cb_arg, linker_base, XDL_UTIL_LINKER_PATHNAME, &linker_load_bias)))
+      return r;
+  }
+
+  // for other ELF
+  uintptr_t pkg[5] = {(uintptr_t)cb, (uintptr_t)cb_arg, (uintptr_t)&maps, linker_load_bias, (uintptr_t)flags};
+  if (__ANDROID_API_L__ == api_level || __ANDROID_API_L_MR1__ == api_level) xdl_linker_lock();
+  r = dl_iterate_phdr(xdl_iterate_by_linker_cb, pkg);
+  if (__ANDROID_API_L__ == api_level || __ANDROID_API_L_MR1__ == api_level) xdl_linker_unlock();
+
+  if (NULL != maps) fclose(maps);
+  return r;
+}
+
+#if (defined(__arm__) || defined(__i386__)) && __ANDROID_API__ < __ANDROID_API_L__
+static int xdl_iterate_by_maps(xdl_iterate_phdr_cb_t cb, void *cb_arg) {
+  FILE *maps = fopen("/proc/self/maps", "r");
+  if (NULL == maps) return 0;
+
+  int r = 0;
+  char buf1[1024], buf2[1024];
+  char *line = buf1;
+  uintptr_t prev_base = 0;
+  bool try_next_line = false;
+
+  while (fgets(line, sizeof(buf1), maps)) {
+    // Try to find an ELF which loaded by linker.
+    uintptr_t base, offset;
+    char exec;
+    if (3 != sscanf(line, "%" SCNxPTR "-%*" SCNxPTR " r%*c%cp %" SCNxPTR " ", &base, &exec, &offset))
+      goto clean;
+
+    if ('-' == exec && 0 == offset) {
+      // r--p
+      prev_base = base;
+      line = (line == buf1 ? buf2 : buf1);
+      try_next_line = true;
+      continue;
+    } else if (exec == 'x') {
+      // r-xp
+      char *pathname = NULL;
+      if (try_next_line && 0 != offset) {
+        char *prev = (line == buf1 ? buf2 : buf1);
+        char *prev_pathname = strchr(prev, '/');
+        if (NULL == prev_pathname) goto clean;
+
+        pathname = strchr(line, '/');
+        if (NULL == pathname) goto clean;
+
+        xdl_util_trim_ending(prev_pathname);
+        xdl_util_trim_ending(pathname);
+        if (0 != strcmp(prev_pathname, pathname)) goto clean;
+
+        // we found the line with r-xp in the next line
+        base = prev_base;
+        offset = 0;
+      }
+
+      if (0 != offset) goto clean;
+
+      // get pathname
+      if (NULL == pathname) {
+        pathname = strchr(line, '/');
+        if (NULL == pathname) goto clean;
+        xdl_util_trim_ending(pathname);
+      }
+
+      if (0 != memcmp((void *)base, ELFMAG, SELFMAG)) goto clean;
+      ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base;
+      struct dl_phdr_info info;
+      info.dlpi_name = pathname;
+      info.dlpi_phdr = (const ElfW(Phdr) *)(base + ehdr->e_phoff);
+      info.dlpi_phnum = ehdr->e_phnum;
+
+      // callback
+      if (0 != (r = xdl_iterate_do_callback(cb, cb_arg, base, pathname, NULL))) break;
+    }
+
+  clean:
+    try_next_line = false;
+  }
+
+  fclose(maps);
+  return r;
+}
+#endif
+
+int xdl_iterate_phdr_impl(xdl_iterate_phdr_cb_t cb, void *cb_arg, int flags) {
+  // iterate by /proc/self/maps in Android 4.x (Android 4.x only supports arm32 and x86)
+#if (defined(__arm__) || defined(__i386__)) && __ANDROID_API__ < __ANDROID_API_L__
+  if (xdl_util_get_api_level() < __ANDROID_API_L__) return xdl_iterate_by_maps(cb, cb_arg);
+#endif
+
+  // iterate by dl_iterate_phdr()
+  return xdl_iterate_by_linker(cb, cb_arg, flags);
+}
+
+int xdl_iterate_get_full_pathname(uintptr_t base, char *buf, size_t buf_len) {
+  FILE *maps = NULL;
+  int r = xdl_iterate_get_pathname_from_maps(base, buf, buf_len, &maps);
+  if (NULL != maps) fclose(maps);
+  return r;
+}
diff --git a/app/src/main/cpp/shadowhook/third_party/xdl/xdl_iterate.h b/app/src/main/cpp/shadowhook/third_party/xdl/xdl_iterate.h
new file mode 100644
index 0000000000000000000000000000000000000000..ab228660b48d785a6613655dd23996eedf9f7c4e
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/third_party/xdl/xdl_iterate.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2020-2023 HexHacking Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by caikelun on 2020-10-04.
+
+#ifndef IO_GITHUB_HEXHACKING_XDL_ITERATE
+#define IO_GITHUB_HEXHACKING_XDL_ITERATE
+
+#include <link.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef int (*xdl_iterate_phdr_cb_t)(struct dl_phdr_info *info, size_t size, void *arg);
+int xdl_iterate_phdr_impl(xdl_iterate_phdr_cb_t cb, void *cb_arg, int flags);
+
+int xdl_iterate_get_full_pathname(uintptr_t base, char *buf, size_t buf_len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/app/src/main/cpp/shadowhook/third_party/xdl/xdl_linker.c b/app/src/main/cpp/shadowhook/third_party/xdl/xdl_linker.c
new file mode 100644
index 0000000000000000000000000000000000000000..9021867cdb9a184ebd9777f3c75c5e1f65217d2c
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/third_party/xdl/xdl_linker.c
@@ -0,0 +1,234 @@
+// Copyright (c) 2020-2023 HexHacking Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by caikelun on 2021-02-21.
+
+#include "xdl_linker.h"
+
+#include <dlfcn.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#include "xdl.h"
+#include "xdl_iterate.h"
+#include "xdl_util.h"
+
+#define XDL_LINKER_SYM_MUTEX           "__dl__ZL10g_dl_mutex"
+#define XDL_LINKER_SYM_DLOPEN_EXT_N    "__dl__ZL10dlopen_extPKciPK17android_dlextinfoPv"
+#define XDL_LINKER_SYM_DO_DLOPEN_N     "__dl__Z9do_dlopenPKciPK17android_dlextinfoPv"
+#define XDL_LINKER_SYM_DLOPEN_O        "__dl__Z8__dlopenPKciPKv"
+#define XDL_LINKER_SYM_LOADER_DLOPEN_P "__loader_dlopen"
+
+#ifndef __LP64__
+#define LIB "lib"
+#else
+#define LIB "lib64"
+#endif
+
+typedef void *(*xdl_linker_dlopen_n_t)(const char *, int, const void *, void *);
+typedef void *(*xdl_linker_dlopen_o_t)(const char *, int, const void *);
+
+static pthread_mutex_t *xdl_linker_mutex = NULL;
+static void *xdl_linker_dlopen = NULL;
+
+typedef enum { MATCH_PREFIX, MATCH_SUFFIX } xdl_linker_match_type_t;
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+typedef struct {
+  xdl_linker_match_type_t type;
+  const char *value;
+} xdl_linker_match_t;
+#pragma clang diagnostic pop
+
+typedef struct {
+  void *addr;
+  xdl_linker_match_t *matches;
+  size_t matches_cursor;
+} xdl_linker_caller_t;
+
+// https://source.android.com/docs/core/architecture/vndk/linker-namespace
+// The following rules are loose and incomplete, you can add more according to your needs.
+static xdl_linker_match_t xdl_linker_match_default[] = {{MATCH_SUFFIX, "/libc.so"}};
+static xdl_linker_match_t xdl_linker_match_art[] = {{MATCH_SUFFIX, "/libart.so"}};
+static xdl_linker_match_t xdl_linker_match_sphal[] = {{MATCH_PREFIX, "/vendor/" LIB "/egl/"},
+                                                      {MATCH_PREFIX, "/vendor/" LIB "/hw/"},
+                                                      {MATCH_PREFIX, "/vendor/" LIB "/"},
+                                                      {MATCH_PREFIX, "/odm/" LIB "/"}};
+static xdl_linker_match_t xdl_linker_match_vndk[] = {{MATCH_PREFIX, "/apex/com.android.vndk.v"},
+                                                     {MATCH_PREFIX, "/vendor/" LIB "/vndk-sp/"},
+                                                     {MATCH_PREFIX, "/odm/" LIB "/vndk-sp/"}};
+static xdl_linker_caller_t xdl_linker_callers[] = {
+    {NULL, xdl_linker_match_default, sizeof(xdl_linker_match_default) / sizeof(xdl_linker_match_t)},
+    {NULL, xdl_linker_match_art, sizeof(xdl_linker_match_art) / sizeof(xdl_linker_match_t)},
+    {NULL, xdl_linker_match_sphal, sizeof(xdl_linker_match_sphal) / sizeof(xdl_linker_match_t)},
+    {NULL, xdl_linker_match_vndk, sizeof(xdl_linker_match_vndk) / sizeof(xdl_linker_match_t)}};
+
+static void xdl_linker_init_symbols_impl(void) {
+  // find linker from: /proc/self/maps (API level < 18) or getauxval (API level >= 18)
+  void *handle = xdl_open(XDL_UTIL_LINKER_BASENAME, XDL_DEFAULT);
+  if (NULL == handle) return;
+
+  int api_level = xdl_util_get_api_level();
+  if (__ANDROID_API_L__ == api_level || __ANDROID_API_L_MR1__ == api_level) {
+    // == Android 5.x
+    xdl_linker_mutex = (pthread_mutex_t *)xdl_dsym(handle, XDL_LINKER_SYM_MUTEX, NULL);
+  } else if (__ANDROID_API_N__ == api_level || __ANDROID_API_N_MR1__ == api_level) {
+    // == Android 7.x
+    xdl_linker_dlopen = xdl_dsym(handle, XDL_LINKER_SYM_DLOPEN_EXT_N, NULL);
+    if (NULL == xdl_linker_dlopen) {
+      xdl_linker_dlopen = xdl_dsym(handle, XDL_LINKER_SYM_DO_DLOPEN_N, NULL);
+      xdl_linker_mutex = (pthread_mutex_t *)xdl_dsym(handle, XDL_LINKER_SYM_MUTEX, NULL);
+    }
+  } else if (__ANDROID_API_O__ == api_level || __ANDROID_API_O_MR1__ == api_level) {
+    // == Android 8.x
+    xdl_linker_dlopen = xdl_dsym(handle, XDL_LINKER_SYM_DLOPEN_O, NULL);
+  } else if (api_level >= __ANDROID_API_P__) {
+    // >= Android 9.0
+    xdl_linker_dlopen = xdl_sym(handle, XDL_LINKER_SYM_LOADER_DLOPEN_P, NULL);
+  }
+
+  xdl_close(handle);
+}
+
+static void xdl_linker_init_symbols(void) {
+  static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+  static bool inited = false;
+  if (!inited) {
+    pthread_mutex_lock(&lock);
+    if (!inited) {
+      xdl_linker_init_symbols_impl();
+      inited = true;
+    }
+    pthread_mutex_unlock(&lock);
+  }
+}
+
+void xdl_linker_lock(void) {
+  xdl_linker_init_symbols();
+
+  if (NULL != xdl_linker_mutex) pthread_mutex_lock(xdl_linker_mutex);
+}
+
+void xdl_linker_unlock(void) {
+  if (NULL != xdl_linker_mutex) pthread_mutex_unlock(xdl_linker_mutex);
+}
+
+static void *xdl_linker_get_caller_addr(struct dl_phdr_info *info) {
+  for (size_t i = 0; i < info->dlpi_phnum; i++) {
+    const ElfW(Phdr) *phdr = &(info->dlpi_phdr[i]);
+    if (PT_LOAD == phdr->p_type) {
+      return (void *)(info->dlpi_addr + phdr->p_vaddr);
+    }
+  }
+  return NULL;
+}
+
+static void xdl_linker_save_caller_addr(struct dl_phdr_info *info, xdl_linker_caller_t *caller,
+                                        size_t cursor) {
+  void *addr = xdl_linker_get_caller_addr(info);
+  if (NULL != addr) {
+    caller->addr = addr;
+    caller->matches_cursor = cursor;
+  }
+}
+
+static int xdl_linker_get_caller_addr_cb(struct dl_phdr_info *info, size_t size, void *arg) {
+  (void)size, (void)arg;
+  if (0 == info->dlpi_addr || NULL == info->dlpi_name) return 0;  // continue
+
+  int ret = 1;  // OK
+  for (size_t i = 0; i < sizeof(xdl_linker_callers) / sizeof(xdl_linker_callers[0]); i++) {
+    xdl_linker_caller_t *caller = &xdl_linker_callers[i];
+    for (size_t j = 0; j < caller->matches_cursor; j++) {
+      xdl_linker_match_t *match = &caller->matches[j];
+      switch (match->type) {
+        case MATCH_PREFIX:
+          if (xdl_util_starts_with(info->dlpi_name, match->value)) {
+            xdl_linker_save_caller_addr(info, caller, j);
+          }
+          break;
+        case MATCH_SUFFIX:
+          if (xdl_util_ends_with(info->dlpi_name, match->value)) {
+            xdl_linker_save_caller_addr(info, caller, j);
+          }
+          break;
+      }
+    }
+    if (NULL == caller->addr || 0 != caller->matches_cursor) ret = 0;  // continue
+  }
+  return ret;
+}
+
+static void xdl_linker_init_caller_addr_impl(void) {
+  xdl_iterate_phdr_impl(xdl_linker_get_caller_addr_cb, NULL, XDL_DEFAULT);
+}
+
+static void xdl_linker_init_caller_addr(void) {
+  static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+  static bool inited = false;
+  if (!inited) {
+    pthread_mutex_lock(&lock);
+    if (!inited) {
+      xdl_linker_init_caller_addr_impl();
+      inited = true;
+    }
+    pthread_mutex_unlock(&lock);
+  }
+}
+
+void *xdl_linker_force_dlopen(const char *filename) {
+  int api_level = xdl_util_get_api_level();
+
+  if (api_level <= __ANDROID_API_M__) {
+    // <= Android 6.0
+    return dlopen(filename, RTLD_NOW);
+  } else {
+    xdl_linker_init_symbols();
+    if (NULL == xdl_linker_dlopen) return NULL;
+    xdl_linker_init_caller_addr();
+
+    void *handle = NULL;
+    if (__ANDROID_API_N__ == api_level || __ANDROID_API_N_MR1__ == api_level) {
+      // == Android 7.x
+      xdl_linker_lock();
+      for (size_t i = 0; i < sizeof(xdl_linker_callers) / sizeof(xdl_linker_callers[0]); i++) {
+        xdl_linker_caller_t *caller = &xdl_linker_callers[i];
+        if (NULL != caller->addr) {
+          handle = ((xdl_linker_dlopen_n_t)xdl_linker_dlopen)(filename, RTLD_NOW, NULL, caller->addr);
+          if (NULL != handle) break;
+        }
+      }
+      xdl_linker_unlock();
+    } else {
+      // >= Android 8.0
+      for (size_t i = 0; i < sizeof(xdl_linker_callers) / sizeof(xdl_linker_callers[0]); i++) {
+        xdl_linker_caller_t *caller = &xdl_linker_callers[i];
+        if (NULL != caller->addr) {
+          handle = ((xdl_linker_dlopen_o_t)xdl_linker_dlopen)(filename, RTLD_NOW, caller->addr);
+          if (NULL != handle) break;
+        }
+      }
+    }
+    return handle;
+  }
+}
diff --git a/app/src/main/cpp/shadowhook/third_party/xdl/xdl_linker.h b/app/src/main/cpp/shadowhook/third_party/xdl/xdl_linker.h
new file mode 100644
index 0000000000000000000000000000000000000000..00cbf44b75db5ff897d32d7ad5f802afdd4ebaba
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/third_party/xdl/xdl_linker.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2020-2023 HexHacking Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by caikelun on 2021-02-21.
+
+#ifndef IO_GITHUB_HEXHACKING_XDL_LINKER
+#define IO_GITHUB_HEXHACKING_XDL_LINKER
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void xdl_linker_lock(void);
+void xdl_linker_unlock(void);
+
+void *xdl_linker_force_dlopen(const char *filename);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/app/src/main/cpp/shadowhook/third_party/xdl/xdl_lzma.c b/app/src/main/cpp/shadowhook/third_party/xdl/xdl_lzma.c
new file mode 100644
index 0000000000000000000000000000000000000000..541300de88c00f6cd7232792c127a568da1eda3f
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/third_party/xdl/xdl_lzma.c
@@ -0,0 +1,187 @@
+// Copyright (c) 2020-2023 HexHacking Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by caikelun on 2020-11-08.
+
+#include "xdl_lzma.h"
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "xdl.h"
+#include "xdl_util.h"
+
+// LZMA library pathname & symbol names
+#ifndef __LP64__
+#define XDL_LZMA_PATHNAME "/system/lib/liblzma.so"
+#else
+#define XDL_LZMA_PATHNAME "/system/lib64/liblzma.so"
+#endif
+#define XDL_LZMA_SYM_CRCGEN     "CrcGenerateTable"
+#define XDL_LZMA_SYM_CRC64GEN   "Crc64GenerateTable"
+#define XDL_LZMA_SYM_CONSTRUCT  "XzUnpacker_Construct"
+#define XDL_LZMA_SYM_ISFINISHED "XzUnpacker_IsStreamWasFinished"
+#define XDL_LZMA_SYM_FREE       "XzUnpacker_Free"
+#define XDL_LZMA_SYM_CODE       "XzUnpacker_Code"
+
+// LZMA data type definition
+#define SZ_OK 0
+typedef struct ISzAlloc ISzAlloc;
+typedef const ISzAlloc *ISzAllocPtr;
+struct ISzAlloc {
+  void *(*Alloc)(ISzAllocPtr p, size_t size);
+  void (*Free)(ISzAllocPtr p, void *address); /* address can be 0 */
+};
+typedef enum {
+  CODER_STATUS_NOT_SPECIFIED,      /* use main error code instead */
+  CODER_STATUS_FINISHED_WITH_MARK, /* stream was finished with end mark. */
+  CODER_STATUS_NOT_FINISHED,       /* stream was not finished */
+  CODER_STATUS_NEEDS_MORE_INPUT    /* you must provide more input bytes */
+} ECoderStatus;
+typedef enum {
+  CODER_FINISH_ANY, /* finish at any point */
+  CODER_FINISH_END  /* block must be finished at the end */
+} ECoderFinishMode;
+
+// LZMA function type definition
+typedef void (*xdl_lzma_crcgen_t)(void);
+typedef void (*xdl_lzma_crc64gen_t)(void);
+typedef void (*xdl_lzma_construct_t)(void *, ISzAllocPtr);
+typedef int (*xdl_lzma_isfinished_t)(const void *);
+typedef void (*xdl_lzma_free_t)(void *);
+typedef int (*xdl_lzma_code_t)(void *, uint8_t *, size_t *, const uint8_t *, size_t *, ECoderFinishMode,
+                               ECoderStatus *);
+typedef int (*xdl_lzma_code_q_t)(void *, uint8_t *, size_t *, const uint8_t *, size_t *, int,
+                                 ECoderFinishMode, ECoderStatus *);
+
+// LZMA function pointor
+static xdl_lzma_construct_t xdl_lzma_construct = NULL;
+static xdl_lzma_isfinished_t xdl_lzma_isfinished = NULL;
+static xdl_lzma_free_t xdl_lzma_free = NULL;
+static void *xdl_lzma_code = NULL;
+
+// LZMA init
+static void xdl_lzma_init() {
+  void *lzma = xdl_open(XDL_LZMA_PATHNAME, XDL_TRY_FORCE_LOAD);
+  if (NULL == lzma) return;
+
+  xdl_lzma_crcgen_t crcgen = NULL;
+  xdl_lzma_crc64gen_t crc64gen = NULL;
+  if (NULL == (crcgen = (xdl_lzma_crcgen_t)xdl_sym(lzma, XDL_LZMA_SYM_CRCGEN, NULL))) goto end;
+  if (NULL == (crc64gen = (xdl_lzma_crc64gen_t)xdl_sym(lzma, XDL_LZMA_SYM_CRC64GEN, NULL))) goto end;
+  if (NULL == (xdl_lzma_construct = (xdl_lzma_construct_t)xdl_sym(lzma, XDL_LZMA_SYM_CONSTRUCT, NULL)))
+    goto end;
+  if (NULL == (xdl_lzma_isfinished = (xdl_lzma_isfinished_t)xdl_sym(lzma, XDL_LZMA_SYM_ISFINISHED, NULL)))
+    goto end;
+  if (NULL == (xdl_lzma_free = (xdl_lzma_free_t)xdl_sym(lzma, XDL_LZMA_SYM_FREE, NULL))) goto end;
+  if (NULL == (xdl_lzma_code = xdl_sym(lzma, XDL_LZMA_SYM_CODE, NULL))) goto end;
+  crcgen();
+  crc64gen();
+
+end:
+  xdl_close(lzma);
+}
+
+// LZMA internal alloc / free
+static void *xdl_lzma_internal_alloc(ISzAllocPtr p, size_t size) {
+  (void)p;
+  return malloc(size);
+}
+static void xdl_lzma_internal_free(ISzAllocPtr p, void *address) {
+  (void)p;
+  free(address);
+}
+
+int xdl_lzma_decompress(uint8_t *src, size_t src_size, uint8_t **dst, size_t *dst_size) {
+  size_t src_offset = 0;
+  size_t dst_offset = 0;
+  size_t src_remaining;
+  size_t dst_remaining;
+  ISzAlloc alloc = {.Alloc = xdl_lzma_internal_alloc, .Free = xdl_lzma_internal_free};
+  long long state[4096 / sizeof(long long)];  // must be enough, 8-bit aligned
+  ECoderStatus status;
+  int api_level = xdl_util_get_api_level();
+
+  // init and check
+  static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+  static bool inited = false;
+  if (!inited) {
+    pthread_mutex_lock(&lock);
+    if (!inited) {
+      xdl_lzma_init();
+      inited = true;
+    }
+    pthread_mutex_unlock(&lock);
+  }
+  if (NULL == xdl_lzma_code) return -1;
+
+  xdl_lzma_construct(&state, &alloc);
+
+  *dst_size = 2 * src_size;
+  *dst = NULL;
+  do {
+    *dst_size *= 2;
+    if (NULL == (*dst = realloc(*dst, *dst_size))) {
+      xdl_lzma_free(&state);
+      return -1;
+    }
+
+    src_remaining = src_size - src_offset;
+    dst_remaining = *dst_size - dst_offset;
+
+    int result;
+    if (api_level >= __ANDROID_API_Q__) {
+      xdl_lzma_code_q_t lzma_code_q = (xdl_lzma_code_q_t)xdl_lzma_code;
+      result = lzma_code_q(&state, *dst + dst_offset, &dst_remaining, src + src_offset, &src_remaining, 1,
+                           CODER_FINISH_ANY, &status);
+    } else {
+      xdl_lzma_code_t lzma_code = (xdl_lzma_code_t)xdl_lzma_code;
+      result = lzma_code(&state, *dst + dst_offset, &dst_remaining, src + src_offset, &src_remaining,
+                         CODER_FINISH_ANY, &status);
+    }
+    if (SZ_OK != result) {
+      free(*dst);
+      xdl_lzma_free(&state);
+      return -1;
+    }
+
+    src_offset += src_remaining;
+    dst_offset += dst_remaining;
+  } while (status == CODER_STATUS_NOT_FINISHED);
+
+  xdl_lzma_free(&state);
+
+  if (!xdl_lzma_isfinished(&state)) {
+    free(*dst);
+    return -1;
+  }
+
+  *dst_size = dst_offset;
+  *dst = realloc(*dst, *dst_size);
+  return 0;
+}
diff --git a/app/src/main/cpp/shadowhook/third_party/xdl/xdl_lzma.h b/app/src/main/cpp/shadowhook/third_party/xdl/xdl_lzma.h
new file mode 100644
index 0000000000000000000000000000000000000000..63c848a61e3e5bcd7a6d3510c645ba802f8f3b36
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/third_party/xdl/xdl_lzma.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2020-2023 HexHacking Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by caikelun on 2020-11-08.
+
+#ifndef IO_GITHUB_HEXHACKING_XDL_LZMA
+#define IO_GITHUB_HEXHACKING_XDL_LZMA
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int xdl_lzma_decompress(uint8_t *src, size_t src_size, uint8_t **dst, size_t *dst_size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/app/src/main/cpp/shadowhook/third_party/xdl/xdl_util.c b/app/src/main/cpp/shadowhook/third_party/xdl/xdl_util.c
new file mode 100644
index 0000000000000000000000000000000000000000..7d9e640db3a724a4fcebaa317457816dc7f56d15
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/third_party/xdl/xdl_util.c
@@ -0,0 +1,95 @@
+// Copyright (c) 2020-2023 HexHacking Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by caikelun on 2020-10-04.
+
+#include "xdl_util.h"
+
+#include <android/api-level.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+bool xdl_util_starts_with(const char *str, const char *start) {
+  while (*str && *str == *start) {
+    str++;
+    start++;
+  }
+
+  return '\0' == *start;
+}
+
+bool xdl_util_ends_with(const char *str, const char *ending) {
+  size_t str_len = strlen(str);
+  size_t ending_len = strlen(ending);
+
+  if (ending_len > str_len) return false;
+
+  return 0 == strcmp(str + (str_len - ending_len), ending);
+}
+
+size_t xdl_util_trim_ending(char *start) {
+  char *end = start + strlen(start);
+  while (start < end && isspace((int)(*(end - 1)))) {
+    end--;
+    *end = '\0';
+  }
+  return (size_t)(end - start);
+}
+
+static int xdl_util_get_api_level_from_build_prop(void) {
+  char buf[128];
+  int api_level = -1;
+
+  FILE *fp = fopen("/system/build.prop", "r");
+  if (NULL == fp) goto end;
+
+  while (fgets(buf, sizeof(buf), fp)) {
+    if (xdl_util_starts_with(buf, "ro.build.version.sdk=")) {
+      api_level = atoi(buf + 21);
+      break;
+    }
+  }
+  fclose(fp);
+
+end:
+  return (api_level > 0) ? api_level : -1;
+}
+
+int xdl_util_get_api_level(void) {
+  static int xdl_util_api_level = -1;
+
+  if (xdl_util_api_level < 0) {
+    int api_level = android_get_device_api_level();
+    if (api_level < 0)
+      api_level = xdl_util_get_api_level_from_build_prop();  // compatible with unusual models
+    if (api_level < __ANDROID_API_J__) api_level = __ANDROID_API_J__;
+
+    __atomic_store_n(&xdl_util_api_level, api_level, __ATOMIC_SEQ_CST);
+  }
+
+  return xdl_util_api_level;
+}
diff --git a/app/src/main/cpp/shadowhook/third_party/xdl/xdl_util.h b/app/src/main/cpp/shadowhook/third_party/xdl/xdl_util.h
new file mode 100644
index 0000000000000000000000000000000000000000..582e99fdb574715521e1856416698f699ae667d8
--- /dev/null
+++ b/app/src/main/cpp/shadowhook/third_party/xdl/xdl_util.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2020-2023 HexHacking Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+// Created by caikelun on 2020-10-04.
+
+#ifndef IO_GITHUB_HEXHACKING_XDL_UTIL
+#define IO_GITHUB_HEXHACKING_XDL_UTIL
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#ifndef __LP64__
+#define XDL_UTIL_LINKER_BASENAME        "linker"
+#define XDL_UTIL_LINKER_PATHNAME        "/system/bin/linker"
+#define XDL_UTIL_APP_PROCESS_BASENAME   "app_process32"
+#define XDL_UTIL_APP_PROCESS_PATHNAME   "/system/bin/app_process32"
+#define XDL_UTIL_APP_PROCESS_BASENAME_K "app_process"
+#define XDL_UTIL_APP_PROCESS_PATHNAME_K "/system/bin/app_process"
+#else
+#define XDL_UTIL_LINKER_BASENAME      "linker64"
+#define XDL_UTIL_LINKER_PATHNAME      "/system/bin/linker64"
+#define XDL_UTIL_APP_PROCESS_BASENAME "app_process64"
+#define XDL_UTIL_APP_PROCESS_PATHNAME "/system/bin/app_process64"
+#endif
+#define XDL_UTIL_VDSO_BASENAME "[vdso]"
+
+#define XDL_UTIL_TEMP_FAILURE_RETRY(exp)   \
+  ({                                       \
+    __typeof__(exp) _rc;                   \
+    do {                                   \
+      errno = 0;                           \
+      _rc = (exp);                         \
+    } while (_rc == -1 && errno == EINTR); \
+    _rc;                                   \
+  })
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+bool xdl_util_starts_with(const char *str, const char *start);
+bool xdl_util_ends_with(const char *str, const char *ending);
+
+size_t xdl_util_trim_ending(char *start);
+
+int xdl_util_get_api_level(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif