13.優(yōu)化 - 線程監(jiān)控(matrix)

??本文來分析下matrix對于線程的監(jiān)控,matrix對于線程的監(jiān)控主要 hook pthread 的pthread_create pthread_detach pthread_join pthread_setname_np 幾個方法医增。

原理

??先來看下為什么 hook pthread 的幾個方法就可以監(jiān)控到線程头岔。

一般的 java 線程寫法

new Thread(new Runable{
    @Override
    void run(){
        // do action 
    }
}).start();


    public synchronized void start() {
        ...
        started = false;
        try {
            //進入到 native 中
            nativeCreate(this, stackSize, daemon);
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
static void Thread_nativeCreate(JNIEnv* env, jclass, jobject java_thread, jlong stack_size,jboolean daemon) {
    ...
  Thread::CreateNativeThread(env, java_thread, stack_size, daemon == JNI_TRUE);
}

void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) {
  CHECK(java_peer != nullptr);
  Thread* self = static_cast<JNIEnvExt*>(env)->GetSelf();

  if (VLOG_IS_ON(threads)) {
    if (java_name != nullptr) {
      thread_name = java_name->ToModifiedUtf8();
    } else {
      thread_name = "(Unnamed)";
    }
  }

  Runtime* runtime = Runtime::Current();

   // pthread 的回調(diào)參數(shù)
  Thread* child_thread = new Thread(is_daemon);
  // 設(shè)置 java 的 thread 類
  child_thread->tlsPtr_.jpeer = env->NewGlobalRef(java_peer);
  stack_size = FixStackSize(stack_size);
  // 給 java thread 的 nativePeer 設(shè)置值為 child_thread 的地址
  env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer,
                    reinterpret_cast<jlong>(child_thread));
  std::string error_msg;
  std::unique_ptr<JNIEnvExt> child_jni_env_ext(
      JNIEnvExt::Create(child_thread, Runtime::Current()->GetJavaVM(), &error_msg));

  int pthread_create_result = 0;
  if (child_jni_env_ext.get() != nullptr) {
    pthread_t new_pthread;
    pthread_attr_t attr;
    child_thread->tlsPtr_.tmp_jni_env = child_jni_env_ext.get();
    // 創(chuàng)建 pthread ,回調(diào)函數(shù)為 Thread::CreateCallback 尤仍,回調(diào)函數(shù)的參數(shù)為 child_thread
    pthread_create_result = pthread_create(&new_pthread,
                                           &attr,
                                           Thread::CreateCallback,
                                           child_thread);
  }
}

void* Thread::CreateCallback(void* arg) {
  Thread* self = reinterpret_cast<Thread*>(arg);
  Runtime* runtime = Runtime::Current();
  {

    runtime->GetRuntimeCallbacks()->ThreadStart(self);

    // Invoke the 'run' method of our java.lang.Thread.
    ObjPtr<mirror::Object> receiver = self->tlsPtr_.opeer;
    jmethodID mid = WellKnownClasses::java_lang_Thread_run;
    ScopedLocalRef<jobject> ref(soa.Env(), soa.AddLocalReference<jobject>(receiver));
    // 執(zhí)行 java thread run 方法
    InvokeVirtualOrInterfaceWithJValues(soa, ref.get(), mid, nullptr);
  }
  // Detach and delete self.
  Runtime::Current()->GetThreadList()->Unregister(self);

  return nullptr;
}

所以可以通過 hook pthread 的一系列方法可以監(jiān)控到線程。

Matrix Thread Hook

主要的 hook 邏輯在 PthreadHook.cpp

  • 開始 hook
    void InstallHooks(bool enable_debug) {
        FETCH_ORIGIN_FUNC(pthread_create)
//        替換
/**
 *         if (!orig_pthread_create) {
            void *handle = dlopen("libc.so", RTLD_LAZY);
            if (handle) {
                orig_pthread_create = (fn_pthread_create_t)dlsym(handle, "pthread_create");
            }
        }
 **/
        // 同上轉(zhuǎn)換
        FETCH_ORIGIN_FUNC(pthread_setname_np)
        FETCH_ORIGIN_FUNC(pthread_detach)
        FETCH_ORIGIN_FUNC(pthread_join)

        if (sThreadTraceEnabled) {
            thread_trace::thread_trace_init();
        }

        matrix::PauseLoadSo();
        xhook_block_refresh();
        {

            int ret = xhook_export_symtable_hook("libc.so", "pthread_create",
                                                 (void *) HANDLER_FUNC_NAME(pthread_create),
                                                 nullptr);
            LOGD(LOG_TAG, "export table hook sym: pthread_create, ret: %d", ret);

            ret = xhook_export_symtable_hook("libc.so", "pthread_setname_np",
                                             (void *) HANDLER_FUNC_NAME(pthread_setname_np),
                                             nullptr);
            LOGD(LOG_TAG, "export table hook sym: pthread_setname_np, ret: %d", ret);
            xhook_grouped_register(HOOK_REQUEST_GROUPID_PTHREAD, ".*/.*\\.so$", "pthread_create",
                                   (void *) HANDLER_FUNC_NAME(pthread_create), nullptr);
            xhook_grouped_register(HOOK_REQUEST_GROUPID_PTHREAD, ".*/.*\\.so$",
                                   "pthread_setname_np",
                                   (void *) HANDLER_FUNC_NAME(pthread_setname_np), nullptr);

            xhook_grouped_register(HOOK_REQUEST_GROUPID_PTHREAD, ".*/.*\\.so$", "pthread_detach",
                                   (void *) HANDLER_FUNC_NAME(pthread_detach), nullptr);
            xhook_grouped_register(HOOK_REQUEST_GROUPID_PTHREAD, ".*/.*\\.so$", "pthread_join",
                                   (void *) HANDLER_FUNC_NAME(pthread_join), nullptr);

            ret = xhook_export_symtable_hook("libc.so", "pthread_detach",
                                             (void *) HANDLER_FUNC_NAME(pthread_detach), nullptr);
            LOGD(LOG_TAG, "export table hook sym: pthread_detach, ret: %d", ret);

            ret = xhook_export_symtable_hook("libc.so", "pthread_join",
                                             (void *) HANDLER_FUNC_NAME(pthread_join), nullptr);
            LOGD(LOG_TAG, "export table hook sym: pthread_join, ret: %d", ret);

            xhook_enable_debug(enable_debug ? 1 : 0);
            xhook_enable_sigsegv_protection(0);
            xhook_refresh(0);
        }
        xhook_unblock_refresh();
        matrix::ResumeLoadSo();
    }
  • 說明

    宏定義在 matrix-commons Model 中的 HookCommon.h 中

// FETCH_ORIGIN_FUNC(pthread_create)
// 是一個宏定義,需要替換還原出代碼
    
// 對應(yīng)的宏
#define HANDLER_FUNC_NAME(fn_name) h_##fn_name
#define ORIGINAL_FUNC_NAME(fn_name) orig_##fn_name
#define RTLD_LAZY     0x00001
#define FUNC_TYPE(sym) fn_##sym##_t
#define FETCH_ORIGIN_FUNC(sym) \
    if (!ORIGINAL_FUNC_NAME(sym)) { \
        void *handle = dlopen(ORIGINAL_LIB, RTLD_LAZY); \
        if (handle) { \
            ORIGINAL_FUNC_NAME(sym) = (FUNC_TYPE(sym))dlsym(handle, #sym); \
        } \
    }
    
// 替換后代碼   
if (!orig_pthread_create) {
    void *handle = dlopen("libc.so", RTLD_LAZY);
    if (handle) {
        orig_pthread_create = (fn_pthread_create_t)dlsym(handle, "pthread_create");
    }
}

DECLARE_HOOK_ORIG(int, pthread_create, pthread_t*, pthread_attr_t const*,
                  pthread_hook::pthread_routine_t, void*);
// 替換后
typedef int (*fn_pthread_create_t)(pthread_t*, pthread_attr_t const*,pthread_hook::pthread_routine_t, void*);
extern fn_pthread_create_t orig_pthread_create;
int h_pthread_create(pthread_t*, pthread_attr_t const*,pthread_hook::pthread_routine_t, void*);


// 再看這句
int ret = xhook_export_symtable_hook("libc.so", "pthread_create",(void *) HANDLER_FUNC_NAME(pthread_create),nullptr);
// 替換后
int ret = xhook_export_symtable_hook("libc.so", "pthread_create",(void *) h_pthread_create,nullptr);

// 而 h_pthread_create 這個函數(shù)也是一個宏定義 對于為
DEFINE_HOOK_FUN(int, pthread_create,
                pthread_t *pthread, pthread_attr_t const *attr,
                pthread_hook::pthread_routine_t start_routine, void *args) {...}
// 替換后
fn_pthread_create_t orig_pthread_create;
int h_pthread_create(pthread_t *pthread, pthread_attr_t const *attr,pthread_hook::pthread_routine_t start_routine, void *args){...}

其余的宏也如此税朴。

  • pthread_create hook

對于 pthread_create 方法,hook 后會執(zhí)行 h_pthread_create 方法

fn_pthread_create_t orig_pthread_create;
int h_pthread_create(pthread_t *pthread, pthread_attr_t const                 *attr,pthread_hook::pthread_routine_t start_routine, void *args){
    Dl_info callerInfo = {};
    bool callerInfoOk = true;
    if (dladdr(__builtin_return_address(0), &callerInfo) == 0) {
        LOGE(LOG_TAG, "%d >> Fail to get caller info.", ::getpid());
        callerInfoOk = false;
    }

    pthread_attr_t tmpAttr;
    if (LIKELY(attr == nullptr)) {
        ...
    } else {
        tmpAttr = *attr;
    }

    int ret = 0;
    if (sThreadTraceEnabled) {
        auto *routine_wrapper = thread_trace::wrap_pthread_routine(start_routine, args);
        // 執(zhí)行原函數(shù)
        CALL_ORIGIN_FUNC_RET(int, tmpRet, pthread_create, pthread, &tmpAttr,
                             routine_wrapper->wrapped_func,
                             routine_wrapper);
        /**
         *if(!orig_pthread_create){
         *    void *handle = dlopen("libc.so", 0x00001);
         *    if (handle) {
         *        orig_pthread_create = (fn_pthread_create_t)dlsym(handle, #pthread_create);
         *    }
         *}
         * int tmpRet = orig_pthread_create(pthread, &tmpAttr,routine_wrapper->wrapped_func,routine_wrapper);
         **/
        ret = tmpRet;
    } else {
        ...
    }
    // 收集信息
    if (LIKELY(ret == 0) && sThreadTraceEnabled) {
        thread_trace::handle_pthread_create(*pthread);
    }

    if (LIKELY(attr == nullptr)) {
        pthread_attr_destroy(&tmpAttr);
    }

    return ret;
}

// notice: 在父線程回調(diào)此函數(shù)
void thread_trace::handle_pthread_create(const pthread_t pthread) {
    const char *arch =
#ifdef __aarch64__
                       "aarch64";
#elif defined __arm__
    "arm";
#endif
    LOGD(TAG, "+++++++ on_pthread_create, %s", arch);

    pid_t tid = pthread_gettid_np(pthread);
    ...

    if (!m_quicken_unwind) {
        const size_t BUF_SIZE         = 1024;
        char         *java_stacktrace = static_cast<char *>(malloc(BUF_SIZE));
        strncpy(java_stacktrace, "(init stacktrace)", BUF_SIZE);
        if (m_java_stacktrace_mutex.try_lock_for(std::chrono::milliseconds(100))) {
            if (java_stacktrace) {
                // 獲取 java 堆棧
                get_java_stacktrace(java_stacktrace, BUF_SIZE);
            }
            m_java_stacktrace_mutex.unlock();
        } else {
            LOGE(TAG, "maybe reentrant!");
        }

        LOGD(TAG, "parent_tid: %d -> tid: %d", pthread_gettid_np(pthread_self()), tid);
        bool recorded = on_pthread_create_locked(pthread, java_stacktrace, false, tid);

        if (!recorded && java_stacktrace) {
            free(java_stacktrace);
        }
    } else {
        ...
    }

//
    rp_release();
    notify_routine(pthread);
    LOGD(TAG, "------ on_pthread_create end");
}

//static std::map<pthread_t, pthread_meta_t> m_pthread_metas;
static inline bool
on_pthread_create_locked(const pthread_t pthread, char *java_stacktrace, bool quicken_unwind,
                         pid_t tid) {
    pthread_meta_lock meta_lock(m_pthread_meta_mutex);
    // always false
    if (m_pthread_metas.count(pthread)) {
        LOGD(TAG, "on_pthread_create: thread already recorded");
        return false;
    }
    // 從 m_pthread_metas 取出 key=pthread 的 pthread_meta_t家制,沒有就創(chuàng)建一個并添加到 m_pthread_metas 中
    pthread_meta_t &meta = m_pthread_metas[pthread];

    meta.tid = tid;

    // 如果還沒 setname, 此時拿到的是父線程的名字, 在 setname 的時候有一次更正機會, 否則繼承父線程名字
    // 如果已經(jīng) setname, 那么此時拿到的就是當(dāng)前創(chuàng)建線程的名字
    meta.thread_name = static_cast<char *>(malloc(sizeof(char) * THREAD_NAME_LEN));
    if (0 != pthread_getname_ext(pthread, meta.thread_name, THREAD_NAME_LEN)) {
        char temp_name[THREAD_NAME_LEN];
        snprintf(temp_name, THREAD_NAME_LEN, "tid-%d", pthread_gettid_np(pthread));
        strncpy(meta.thread_name, temp_name, THREAD_NAME_LEN);
    }

    LOGD(TAG, "on_pthread_create: pthread = %ld, thread name: %s, %llu", pthread, meta.thread_name,
         (uint64_t) tid);
    // 將線程名字匹配正則表達(dá)式 ".*" 正林,成功就加入
    if (test_match_thread_name(meta)) {
        m_filtered_pthreads.insert(pthread);
    }

    uint64_t native_hash = 0;
    uint64_t java_hash   = 0;
    // 利用 wechat_backtrace 獲取 native 堆棧
    if (quicken_unwind) {
        meta.unwind_mode = wechat_backtrace::Quicken;
        wechat_backtrace::quicken_based_unwind(meta.native_backtrace.frames.get(),
                                               meta.native_backtrace.max_frames,
                                               meta.native_backtrace.frame_size);
    } else {
        meta.unwind_mode = wechat_backtrace::get_backtrace_mode();
        wechat_backtrace::unwind_adapter(meta.native_backtrace.frames.get(),
                                         meta.native_backtrace.max_frames,
                                         meta.native_backtrace.frame_size);
    }
    native_hash = hash_backtrace_frames(&(meta.native_backtrace));


    if (java_stacktrace) {
        meta.java_stacktrace.store(java_stacktrace);
        java_hash = hash_str(java_stacktrace);
        LOGD(TAG, "on_pthread_create: java hash = %llu", (wechat_backtrace::ullint_t) java_hash);
    }

    LOGD(TAG, "on_pthread_create: pthread = %ld, thread name: %s end.", pthread, meta.thread_name);
    // 合并
    if (native_hash || java_hash) {
        meta.hash = hash_combine(native_hash, java_hash);
    }

    return true;
}

??總的來說有兩點,一是在 m_pthread_metas 這個 map 中記錄 pthread_t 為 key , pthread_meta_t 為 value 的信息颤殴,pthread_meta_t 可記錄 java/native 堆棧 觅廓、tid、thread_name 及 hash 信息涵但。二是將符合正則表達(dá)式(".*")的 metas.thread_name 記錄到 m_filtered_pthreads(set) 中杈绸。

  • pthread_setname_np hook
ret = xhook_export_symtable_hook("libc.so", "pthread_setname_np",
                                             (void *) HANDLER_FUNC_NAME(pthread_setname_np),//h_pthread_setname_np
                                             nullptr);

DEFINE_HOOK_FUN(int, pthread_setname_np, pthread_t pthread, const char *name) {
    CALL_ORIGIN_FUNC_RET(int, ret, pthread_setname_np, pthread, name);
    if (LIKELY(ret == 0) && sThreadTraceEnabled) {
        thread_trace::handle_pthread_setname_np(pthread, name);
    }
    return ret;
}
/**
 *  設(shè)置線程的名字
    fn_pthread_setname_np_t orig_pthread_setname_np;
    int h_pthread_setname_np(pthread_t pthread, const char *name){
        if (!orig_pthread_setname_np) {
            void *handle = dlopen("libc.so", 0x01);
            if (handle) {
                orig_pthread_setname_np = (fn_pthread_setname_np_t)dlsym(handle, pthread_setname_np);
            }
        }
        if (LIKELY(ret == 0) && sThreadTraceEnabled) {
            thread_trace::handle_pthread_setname_np(pthread, name);
        }
        return ret;
    }
**/


/**
 * ~~handle_pthread_setname_np 有可能在 handle_pthread_create 之前先執(zhí)行~~
 * 在增加了 cond 之后, 必然后于 on_pthread_create 執(zhí)行
 *
 * @param pthread
 * @param name
 */
void thread_trace::handle_pthread_setname_np(pthread_t pthread, const char *name) {
    if (NULL == name) {
        LOGE(TAG, "setting name null");
        return;
    }

    const size_t name_len = strlen(name);

    if (0 == name_len || name_len >= THREAD_NAME_LEN) {
        LOGE(TAG, "pthread name is illegal, just ignore. len(%s)", name);
        return;
    }

    LOGD(TAG, "++++++++ pre handle_pthread_setname_np tid: %d, %s", pthread_gettid_np(pthread),
         name);

    {
        pthread_meta_lock meta_lock(m_pthread_meta_mutex);

        if (!m_pthread_metas.count(pthread)) { // always false
            // 到這里說明沒有回調(diào) on_pthread_create, setname 對 on_pthread_create 是可見的
            auto lost_thread_name = static_cast<char *>(malloc(sizeof(char) * THREAD_NAME_LEN));
            // 拿到 pthread 的名字
            pthread_getname_ext(pthread, lost_thread_name, THREAD_NAME_LEN);
            LOGE(TAG,
                 "handle_pthread_setname_np: pthread hook lost: {%s} -> {%s}, maybe on_create has not been called",
                 lost_thread_name, name);
            free(lost_thread_name);
            return;
        }

        // 到這里說明 on_pthread_create 已經(jīng)回調(diào)了, 需要修正并檢查新的線程名是否 match 正則
        pthread_meta_t &meta = m_pthread_metas.at(pthread);

        // 設(shè)置名字
        strncpy(meta.thread_name, name, THREAD_NAME_LEN);

        bool parent_match = m_filtered_pthreads.count(pthread) != 0;

        // 如果新線程名不 match, 但父線程名 match, 說明需要從 filter 集合中移除
        if (!test_match_thread_name(meta) && parent_match) {
            m_filtered_pthreads.erase(pthread);
            LOGD(TAG, "--------------------------");
            return;
        }

        // 如果新線程 match, 但父線程名不 match, 說明需要添加僅 filter 集合
        if (test_match_thread_name(meta) && !parent_match) {
            m_filtered_pthreads.insert(pthread);
            LOGD(TAG, "--------------------------");
            return;
        }
    }

    // 否則, 啥也不干 (都 match, 都不 match)
    LOGD(TAG, "--------------------------");
}

??找到了 m_pthread_metas 中的 pthread_meta_t,對其修改了名字贤笆,最后根據(jù)新改的名字是否匹配正則(".*") 和父線程名是否匹配蝇棉,對 m_filtered_pthreads 進行了刪除和增加。

  • pthread_detach 和 pthread_join hook hook
DEFINE_HOOK_FUN(int, pthread_detach, pthread_t pthread) {
    CALL_ORIGIN_FUNC_RET(int, ret, pthread_detach, pthread);
    LOGD(LOG_TAG, "pthread_detach : %d", ret);
    if (LIKELY(ret == 0) && sThreadTraceEnabled) {
        thread_trace::handle_pthread_release(pthread);
    }
    return ret;
}

DEFINE_HOOK_FUN(int, pthread_join, pthread_t pthread, void **return_value_ptr) {
    CALL_ORIGIN_FUNC_RET(int, ret, pthread_join, pthread, return_value_ptr);
    LOGD(LOG_TAG, "pthread_join : %d", ret);
    if (LIKELY(ret == 0) && sThreadTraceEnabled) {
        thread_trace::handle_pthread_release(pthread);
    }
    return ret;
}

void thread_trace::handle_pthread_release(pthread_t pthread) {
    LOGD(TAG, "handle_pthread_release");
    if (!m_trace_pthread_release) {
        LOGD(TAG, "handle_pthread_release disabled");
        return;
    }
    on_pthread_release(pthread);
}

static void on_pthread_release(pthread_t pthread) {
    LOGD(TAG, "on_pthread_release");
    pthread_meta_lock meta_lock(m_pthread_meta_mutex);

    if (!m_pthread_metas.count(pthread)) {
        LOGD(TAG, "on_pthread_exit: thread not found");
        return;
    }
    
    erase_meta(m_pthread_metas, pthread, m_pthread_metas.at(pthread));

    LOGD(TAG, "on_pthread_release end");
}

static void erase_meta(std::map<pthread_t, pthread_meta_t> &metas, pthread_t &pthread, pthread_meta_t &meta) {
    free(meta.thread_name);

    char *java_stacktrace = meta.java_stacktrace.load(std::memory_order_acquire);
    if (java_stacktrace) {
        free(java_stacktrace);
    }

    m_pthread_metas.erase(pthread);
}

??在 m_pthread_metas 刪除了 key=pthread 的數(shù)據(jù)芥永。

dump pthread json_file

static inline void pthread_dump_json_impl(FILE *log_file) {//log_file 需要記錄到該文件中

    LOGD(TAG, "pthread dump waiting count: %zu", m_pthread_routine_flags.size());

    std::map<uint64_t, std::vector<pthread_meta_t>> pthread_metas_not_exited;

    for (auto &i : m_filtered_pthreads) {
        auto &meta = m_pthread_metas[i];
        if (meta.hash) {
            auto &hash_bucket = pthread_metas_not_exited[meta.hash];
            hash_bucket.emplace_back(meta);
        }
    }

    std::map<uint64_t, std::vector<pthread_meta_t>> pthread_metas_not_released;

    for (auto &i : m_pthread_metas) {
        auto &meta = i.second;
        if (!meta.exited) { //一般為 false
            continue;
        }
        if (meta.hash) {
            auto &hash_bucket = pthread_metas_not_released[meta.hash];
            hash_bucket.emplace_back(meta);
        }
    }

    char  *json_str                        = NULL;
    cJSON *json_array_threads_not_exited   = NULL;
    cJSON *json_array_threads_not_released = NULL;
    bool  success                          = false;

    cJSON *json_obj = cJSON_CreateObject();

    if (!json_obj) {
        goto err;
    }

    json_array_threads_not_exited = cJSON_AddArrayToObject(json_obj, "PthreadHook_not_exited");
    json_array_threads_not_released = cJSON_AddArrayToObject(json_obj, "PthreadHook_not_released");

    if (!json_array_threads_not_exited || !json_array_threads_not_released) {
        goto err;
    }
    // 將 pthread_metas_not_exited 的內(nèi)容加入到 json_array_threads_not_exited 中
    success &= append_meta_2_json_array(json_array_threads_not_exited, pthread_metas_not_exited);
    success &= append_meta_2_json_array(json_array_threads_not_released, pthread_metas_not_released);

    json_str = cJSON_PrintUnformatted(json_obj);

    cJSON_Delete(json_obj);

    flogger0(log_file, "%s", json_str);
    cJSON_free(json_str);
    return;

    err:
    LOGD(TAG, "ERROR: create cJSON object failed");
    cJSON_Delete(json_obj);
    return;
}

??將 m_filtered_pthreads 中記錄的數(shù)據(jù)和 m_pthread_metas 中 metas.exited=true 的數(shù)據(jù)輸出到一個 json_log 文件中篡殷。文件類似與

{
    "PthreadHook_not_exited":
    [
        {
            "hash": "614404817278743815",
            "native": "#pc cda00 (null) (/data/app/com.tencent.matrix.test.memoryhook-ns8hChwbmXvfAOKF9wkrHw==/lib/arm64/libwechatbacktrace.so);",
            "java": "dalvik.system.VMStack.getThreadStackTrace(Native Method);com.tencent.matrix.hook.HookManager.getStack(HookManager.java:163);",
            "count": "2",
            "threads":
            [
                {
                    "tid": "6673",
                    "name": "Test"
                },
                {
                    "tid": "5962",
                    "name": "Test"
                }
            ]
        }
    ],
    "PthreadHook_not_released":
    []
}

over

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市埋涧,隨后出現(xiàn)的幾起案子板辽,更是在濱河造成了極大的恐慌,老刑警劉巖棘催,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件劲弦,死亡現(xiàn)場離奇詭異,居然都是意外死亡醇坝,警方通過查閱死者的電腦和手機邑跪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人画畅,你說我怎么就攤上這事砸琅。” “怎么了轴踱?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵症脂,是天一觀的道長。 經(jīng)常有香客問我淫僻,道長诱篷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任雳灵,我火速辦了婚禮棕所,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘细办。我一直安慰自己橙凳,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布笑撞。 她就那樣靜靜地躺著岛啸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪茴肥。 梳的紋絲不亂的頭發(fā)上坚踩,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音瓤狐,去河邊找鬼瞬铸。 笑死,一個胖子當(dāng)著我的面吹牛础锐,可吹牛的內(nèi)容都是我干的嗓节。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼皆警,長吁一口氣:“原來是場噩夢啊……” “哼拦宣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起信姓,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤鸵隧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后意推,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體豆瘫,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年菊值,在試婚紗的時候發(fā)現(xiàn)自己被綠了外驱。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片育灸。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖略步,靈堂內(nèi)的尸體忽然破棺而出描扯,到底是詐尸還是另有隱情,我是刑警寧澤趟薄,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站典徊,受9級特大地震影響杭煎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜卒落,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一羡铲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧儡毕,春花似錦也切、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至费坊,卻和暖如春倒槐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背附井。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工讨越, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人永毅。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓把跨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親沼死。 傳聞我的和親對象是個殘疾皇子着逐,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容