JNI Crash:異常定位與捕獲處理

crash
關(guān)鍵詞:JNI Crash,異常檢測(cè)遵绰,信號(hào)量捕獲

在Android JNI開發(fā)中,經(jīng)常會(huì)遇到JNI崩潰的問題增淹,尤其帶代碼量大椿访,或者嵌入了第三方代碼的情況下,很難進(jìn)行問題定位和處理虑润。本文將介紹兩種常見的JNI崩潰處理方法成玫,包括:

  1. 每個(gè)JNI調(diào)用后進(jìn)行異常檢測(cè)處理(適用于JNI代碼量很小的情況)
  2. 捕獲系統(tǒng)崩潰的Signal,并進(jìn)行異常處理(適用于JNI代碼量大拳喻,難以每句話后面都進(jìn)行異常檢測(cè)的情況)

本文github源碼地址

下面將分別介紹兩種方法:

方法一:ExceptionCheck機(jī)制

首先需要理解的是哭当,JNI沒有try...catch...finally機(jī)制,不能利用這種方法將整段的代碼進(jìn)行異常捕獲冗澈。

在JNI調(diào)用中钦勘,如果發(fā)生異常,程序并不會(huì)停止執(zhí)行亚亲,而是繼續(xù)執(zhí)行下一句代碼彻采,直到崩潰發(fā)生。正確的處理方法是在每一句JNI調(diào)用后面都通過ExceptionCheck函數(shù)手動(dòng)檢測(cè)是否發(fā)生了異常捌归,如果檢測(cè)到異常肛响,進(jìn)行異常處理。如下:

JNIEXPORT jint JNICALL Java_jack_com_jniexceptionhandler_Calculate_jniDivide
  (JNIEnv * env, jobject jobj, jint m, jint n) {
    char* a = NULL;
    int val1 = a[1] - '0';

    // 每句jni執(zhí)行之后都加入異常檢查
    if (checkExc(env)) {
        LOGE("jni exception happened at p0");
        JNU_ThrowByName(env, "java/lang/Exception", "exception from jni: jni exception happened at p0");
        return -1;
    }

    char* b = NULL;
    int val2 = b[1] - '0';

    // 每句jni執(zhí)行之后都加入異常檢查
    if (checkExc(env)) {
        LOGE("jni exception happened at p1");
        JNU_ThrowByName(env, "java/lang/Exception", "exception from jni: jni exception happened at p1");
        return -1;
    }
    return val1/val2;
}

這里在每次JNI調(diào)用之后都要檢測(cè)是否發(fā)生了異常惜索,檢測(cè)函數(shù)checkExec實(shí)現(xiàn)如下:

int checkExc(JNIEnv *env) {
    if(env->ExceptionCheck()) {
        env->ExceptionDescribe(); // writes to logcat
        env->ExceptionClear();
        return 1;
    }
    return -1;
}

如果檢測(cè)到異常特笋,可以在JNI層將異常拋出到Java層進(jìn)行處理,JNI代碼如下:

void JNU_ThrowByName(JNIEnv *env, const char *name, const char *msg)
{
     // 查找異常類
     jclass cls = env->FindClass(name);
     /* 如果這個(gè)異常類沒有找到巾兆,VM會(huì)拋出一個(gè)NowClassDefFoundError異常 */
     if (cls != NULL) {
         env->ThrowNew(cls, msg);  // 拋出指定名字的異常
     }
     /* 釋放局部引用 */
     env->DeleteLocalRef(cls);
 }

這樣猎物,JNI拋出的異常就可以在Java層通過Try...Catch捕獲,并進(jìn)行相應(yīng)的出錯(cuò)提示臼寄,Java層代碼如下:

public static int callJniDivide(int input1, int input2) {
        try {
            return jniDivide(input1, input2);
        } catch (Exception e) {
            Log.e("JniExceptionHandler", e.toString());
            return -1;
        }
    }

這種方法適用于JNI代碼完全可控霸奕,并且體量比較小的情況,也就是你可以預(yù)測(cè)到哪些JNI語句可能會(huì)導(dǎo)致異常吉拳,從而在這些語句后面加入異常檢測(cè)和處理质帅。對(duì)于代碼量大,或者JNI里使用了三方代碼的情況留攒,這種異常檢測(cè)的方法很難實(shí)施煤惩,因?yàn)檫@種情況下你可能沒法找出所有可能出異常的點(diǎn),或者你壓根兒不清楚三方庫的代碼邏輯炼邀,也就不能準(zhǔn)確找出插入異常檢測(cè)代碼段的地方魄揉。這時(shí)候可以使用下面我們要介紹的方法二:信號(hào)量捕獲機(jī)制。

方法二:信號(hào)量捕獲機(jī)制

信號(hào)量捕獲機(jī)制是建立在Linux系統(tǒng)底層的信號(hào)機(jī)制之上的方法拭宁,系統(tǒng)層會(huì)在發(fā)生崩潰的時(shí)候發(fā)送一些特定信號(hào)洛退,通過捕獲并處理這些特定信號(hào)瓣俯,我們就能夠避免JNI crash的發(fā)生,從而相對(duì)優(yōu)雅的結(jié)束程序的執(zhí)行兵怯,缺點(diǎn)是我們只知道JNI代碼發(fā)生了崩潰彩匕,沒有辦法知道具體是哪句代碼導(dǎo)致了崩潰。不過當(dāng)面對(duì)龐大復(fù)雜的JNI代碼時(shí)媒区,利用信號(hào)量捕獲無法預(yù)知的崩潰驼仪,從而避免Crash的發(fā)生,也是非常有意義的袜漩。

下面首先介紹一些Linux信號(hào)量機(jī)制的原理和基本的操作方法绪爸。這里有兩個(gè)知識(shí)點(diǎn),一是如何捕獲特定的信號(hào)量宙攻,二是如何實(shí)現(xiàn)代碼的控制點(diǎn)跳轉(zhuǎn)奠货。

基礎(chǔ)知識(shí)一:信號(hào)量機(jī)制

Signal是傳遞到進(jìn)程(Process)的軟件中斷信號(hào),操作系統(tǒng)通過信號(hào)向一個(gè)正在執(zhí)行的程序報(bào)告一些預(yù)期的狀況座掘,比如引用了無效的地址仇味,或者報(bào)告一個(gè)異步事件的完成。

GNU C庫定義了一系列的信號(hào)類型雹顺,有些信號(hào)標(biāo)志著程序無法繼續(xù)正常執(zhí)行丹墨,這些信號(hào)就會(huì)終止程序執(zhí)行,另外一些信號(hào)則可以默認(rèn)忽略掉嬉愧。

如果你的程序有可能觸發(fā)信號(hào)贩挣,那你可以定義一個(gè)handler,當(dāng)信號(hào)發(fā)生時(shí)没酣,調(diào)用這個(gè)handker代碼進(jìn)行處理王财。

一個(gè)進(jìn)程可以向另外一個(gè)進(jìn)程發(fā)送信號(hào),這使得父進(jìn)程可以終止子進(jìn)程裕便,或者兩個(gè)相關(guān)聯(lián)的進(jìn)程可以通過發(fā)送信號(hào)實(shí)現(xiàn)交流和同步绒净。

生成信號(hào)的事件(event)可以歸納為三個(gè)類型:錯(cuò)誤,外部事件偿衰,或者顯示的請(qǐng)求挂疆。

Signal生成(generated)之后變成pending狀態(tài),通常pending很短的時(shí)間之后就會(huì)發(fā)送到訂閱了這個(gè)信號(hào)的進(jìn)程下翎,但是如果這個(gè)Signal被阻塞(blocked)了的話缤言,那就可以長(zhǎng)時(shí)間處于pending狀態(tài),直到取消阻塞(unblock)视事。一旦取消阻塞胆萧,信號(hào)就會(huì)立即被發(fā)送出去。

收到信號(hào)后通常有三種處理俐东,忽略這個(gè)信號(hào)跌穗、采取默認(rèn)的動(dòng)作订晌、或者定義一個(gè)handler進(jìn)行處理。通過signal或者sigaction函數(shù)可以定義進(jìn)行處理的handler蚌吸,我們稱之為handler捕獲了這個(gè)信號(hào)腾仅。

標(biāo)準(zhǔn)的信號(hào)分為七個(gè)類別,包括:
Program Error Signals
Termination Signals
Alarm Signals
Asynchronous I/O Signals
Job Control Signals
Operation Error Signals等

其中我們主要關(guān)注的是Program Error Signals套利。

可以使用signal或sigaction函數(shù)指定處理信號(hào)的動(dòng)作,后者較前者更靈活鹤耍,可以控制的更加細(xì)膩肉迫。

signal函數(shù)使用

sighandler_t signal (int signum, sighandler t action)

上面是signal函數(shù)的定義,第一個(gè)參數(shù)是要捕獲的信號(hào)稿黄,第二個(gè)是采取的動(dòng)作喊衫,上面講到動(dòng)作分三類:忽略、默認(rèn)動(dòng)作或者自定義的handler杆怕,分別對(duì)應(yīng)第二個(gè)參數(shù)為SIG_DFL族购、SIG_IGN或者自定義的handler函數(shù)。函數(shù)返回的是之前對(duì)這個(gè)信號(hào)設(shè)置的動(dòng)作陵珍,注意寝杖,是之前設(shè)置的動(dòng)作,不是本次設(shè)置的動(dòng)作互纯。
自定義hander函數(shù)格式如下:

void handler (int signum) { ... }

使用signal函數(shù)如下:

      #include <signal.h>
      void termination_handler (int signum)
      {
        struct temp_file *p;
        for (p = temp_file_list; p; p = p->next)
          unlink (p->name);
      }
      int main (void)
      {
        ...
        if (signal (SIGINT, termination_handler) == SIG_IGN)
              signal (SIGINT, SIG_IGN);
        if (signal (SIGHUP, termination_handler) == SIG_IGN)
              signal (SIGHUP, SIG_IGN);
        if (signal (SIGTERM, termination_handler) == SIG_IGN)
              signal (SIGTERM, SIG_IGN);
        ...
}

在上面的代碼中瑟幕,設(shè)置新的動(dòng)作的之后,判斷舊的動(dòng)作是不是忽略(SIG_IGN)留潦,如果是的話只盹,恢復(fù)成就的動(dòng)作,也就是先設(shè)置了新的動(dòng)作兔院,如果發(fā)現(xiàn)舊的動(dòng)作是忽略殖卑,就又設(shè)置回去。

注意坊萝,在BSD信號(hào)安裝(signal)以后需要顯示的卸載孵稽,而SVID系統(tǒng)(sysv_signal)上則不需要。為了同時(shí)兼容不同的情況十偶,sigaction是個(gè)更好的選擇肛冶,應(yīng)該盡量使用sigaction。

sigaction的使用

sigaction是signal的升級(jí)版扯键,比signal函數(shù)提供更精細(xì)的控制睦袖。跟該功能相關(guān)的有一個(gè)結(jié)構(gòu)體和一個(gè)函數(shù),名稱都叫sigaction(奇怪H傩獭):

struct sigaction {
    sighandler_t sa_handler馅笙; // 和signal函數(shù)一樣伦乔,這里可以是默認(rèn)(SIG_DFL), 忽略(SIG_IGN)或者一個(gè)handler函數(shù)指針
    sigset_t sa_mask;// handler處理信號(hào)過程中董习,需要被阻塞的信號(hào)集合
    int sa_flags烈和; // 提供了多種多樣的標(biāo)志,可以影響信號(hào)的表現(xiàn)
}皿淋;

int sigaction (int signum, const struct sigaction *restrict action, struct sigaction *restrict old-action)

在函數(shù)sigaction中招刹,用到了結(jié)構(gòu)體sigaction。
在結(jié)構(gòu)體sigaction中窝趣,sa_handler可以是默認(rèn)(SIG_DFL), 忽略(SIG_IGN)或者一個(gè)handler函數(shù)指針疯暑,這和signal用法是一樣的。sa_mask是handler處理信號(hào)過程中哑舒,需要被阻塞的信號(hào)集合妇拯,正在被處理的信號(hào)類型不需要加入到這個(gè)集合中,因?yàn)樗鼤?huì)自動(dòng)被阻塞洗鸵,只有正在被處理的信號(hào)之外的類型才需要加入阻塞信號(hào)集中越锈。
在函數(shù)sigaction中,第一個(gè)參數(shù)是要處理的信號(hào)膘滨,第二個(gè)參數(shù)就是上面提到的結(jié)構(gòu)體sigaction甘凭,里面包含了處理該信號(hào)需要執(zhí)行的動(dòng)作,和期間需要阻塞的信號(hào)集火邓。第三個(gè)參數(shù)返回的是舊的sigaction結(jié)構(gòu)體对蒲。第二第三個(gè)參數(shù)可以分別或者同事設(shè)置成NULL,表明同時(shí)設(shè)置新的動(dòng)作贡翘,查詢舊的動(dòng)作蹈矮,或者只執(zhí)行一種。

下面使用sigaction實(shí)現(xiàn)前面signal函數(shù)的動(dòng)能:

      #include <signal.h>
      void termination_handler (int signum)
      {
          struct temp_file *p;
          for (p = temp_file_list; p; p = p->next)
              unlink (p->name);
      }

      int main (void)
      {
          ...
          struct sigaction new_action, old_action;
          /* Set up the structure to specify the new action. */ 
          new_action.sa_handler = termination_handler; 
          sigemptyset (&new_action.sa_mask); 
          new_action.sa_flags = 0;
          sigaction (SIGINT, NULL, &old_action);
          if (old_action.sa_handler != SIG_IGN)
              sigaction (SIGINT, &new_action, NULL);
          sigaction (SIGHUP, NULL, &old_action);
          if (old_action.sa_handler != SIG_IGN)
              sigaction (SIGHUP, &new_action, NULL);
          sigaction (SIGTERM, NULL, &old_action);
          if (old_action.sa_handler != SIG_IGN)
              sigaction (SIGTERM, &new_action, NULL);
        ...
}

block signal的兩種方式:sigprocmask或者sigaction的sa_mask鸣驱。兩者的區(qū)別在于block發(fā)生的時(shí)機(jī):
sa_mask方式只會(huì)在handler執(zhí)行的時(shí)候block信號(hào)集中的信號(hào)泛鸟;
sigprocmask方式會(huì)block兩個(gè)sigprocmask之間代碼段執(zhí)行時(shí)的信號(hào)。

優(yōu)先使用sigprocmask和sa_mask方法踊东,代碼更簡(jiǎn)潔北滥,可讀性強(qiáng)。

兩種方式的樣例代碼如下:
使用sigprocmask來阻塞主程序關(guān)鍵代碼執(zhí)行過程中到達(dá)的信號(hào):

      /* This variable is set by the SIGALRM signal handler. */ 
      volatile sig_atomic_t flag = 0;
      int main (void)
      {
          sigset_t block_alarm;
          ...
          /* Initialize the signal mask. */ 
          sigemptyset (&block_alarm); 
          sigaddset(&block_alarm, SIGALRM);
          while (1) {
              /* Check if a signal has arrived; if so, reset the flag. */ 
              sigprocmask (SIG_BLOCK, &block_alarm, NULL);
              if (flag)
              {
                  flag = 0; 
              }
              sigprocmask (SIG_UNBLOCK, &block_alarm, NULL);
              ... 
          }
          actions-if-not-arrived
}

使用sa_mask阻塞handler函數(shù)處理過程中到達(dá)的信號(hào):

      #include <signal.h>
      #include <stddef.h>
      void catch_stop ();
      void install_handler (void)
      {
            struct sigaction setup_action;
            sigset_t block_mask;
            sigemptyset (&block_mask);
            /* Block other terminal-generated signals while handler runs. */ 
            sigaddset (&block_mask, SIGINT);
            sigaddset (&block_mask, SIGQUIT); 
            setup_action.sa_handler = catch_stop; 
            setup_action.sa_mask = block_mask;
            setup_action.sa_flags = 0;
            sigaction (SIGTSTP, &setup_action, NULL);
      }

基礎(chǔ)知識(shí)二:Non-Local Exits

Non-Local Exists指的是嵌套很深的jni代碼發(fā)生異常后沒必要一層層的進(jìn)入父函數(shù)進(jìn)行異常處理闸翅,而是可以直接跳轉(zhuǎn)到最外層指定的代碼錨點(diǎn)進(jìn)行異常處理再芋。
有兩種應(yīng)用場(chǎng)景:
場(chǎng)景一:就是上面提到的在嵌套很深的地方發(fā)生異常后,簡(jiǎn)化異常處理坚冀,直接跳到最外層進(jìn)行處理济赎。
場(chǎng)景二:特定信號(hào)的handler捕捉到Signal后,跳轉(zhuǎn)到主函數(shù)的特定代碼段進(jìn)行出錯(cuò)處理。

      #include <setjmp.h>
      #include <stdlib.h>
      #include <stdio.h>
      sigjmp_buf main_loop; // 代碼錨點(diǎn)標(biāo)志

      int main (void)
      {
        while (1)
              if (sigsetjmp (main_loop))  // 代碼錨點(diǎn)
                  puts ("Back at main loop....");
              else
                  do_command ();
      }

      void do_command (void)
      {
            char buffer[128];
            if (fgets (buffer, 128, stdin) == NULL)
                  siglongjmp (main_loop,  -1); // 跳轉(zhuǎn)到錨點(diǎn)執(zhí)行代碼
            else
                  exit (EXIT_SUCCESS);
      }

利用上面的兩個(gè)知識(shí)點(diǎn)通過信號(hào)量進(jìn)行Android jni崩潰捕獲和處理

有了上面的基礎(chǔ)司训,我們就可以通過捕捉系統(tǒng)信號(hào)量進(jìn)行JNI崩潰捕獲了构捡。完整的代碼如下:

#include <signal.h>
#include <setjmp.h>
#include <pthread.h>

/*
jni捕獲異常的方法之二:捕捉系統(tǒng)崩潰信號(hào),適用于代碼量大的情況壳猜。
*/

// 定義代碼跳轉(zhuǎn)錨點(diǎn)
sigjmp_buf JUMP_ANCHOR;
volatile sig_atomic_t error_cnt = 0;

void exception_handler(int errorCode){
      error_cnt += 1;
      LOGE("JNI_ERROR, error code %d, cnt %d", errorCode, error_cnt);

      // DO SOME CLEAN STAFF HERE...

      // jump to main function to do exception process
      siglongjmp(JUMP_ANCHOR, 1);
}

jint process(JNIEnv * env, jobject jobj, jint m, jint n) {
    char* a = NULL;
    int val1 = a[1] - '0';

    char* b = NULL;
    int val2 = b[1] - '0';

    LOGE("val 1 %d", val1);
    return val1/val2;
}

JNIEXPORT jint JNICALL Java_trio_com_jniexceptionhandler_Calculate2_jniDivide
  (JNIEnv * env, jobject jobj, jint m, jint n) {
  // 注冊(cè)需要捕獲的異常信號(hào)
        /*
         1    HUP Hangup                        33     33 Signal 33
         2    INT Interrupt                     34     34 Signal 34
         3   QUIT Quit                          35     35 Signal 35
         4    ILL Illegal instruction           36     36 Signal 36
         5   TRAP Trap                          37     37 Signal 37
         6   ABRT Aborted                       38     38 Signal 38
         7    BUS Bus error                     39     39 Signal 39
         8    FPE Floating point exception      40     40 Signal 40
         9   KILL Killed                        41     41 Signal 41
        10   USR1 User signal 1                 42     42 Signal 42
        11   SEGV Segmentation fault            43     43 Signal 43
        12   USR2 User signal 2                 44     44 Signal 44
        13   PIPE Broken pipe                   45     45 Signal 45
        14   ALRM Alarm clock                   46     46 Signal 46
        15   TERM Terminated                    47     47 Signal 47
        16 STKFLT Stack fault                   48     48 Signal 48
        17   CHLD Child exited                  49     49 Signal 49
        18   CONT Continue                      50     50 Signal 50
        19   STOP Stopped (signal)              51     51 Signal 51
        20   TSTP Stopped                       52     52 Signal 52
        21   TTIN Stopped (tty input)           53     53 Signal 53
        22   TTOU Stopped (tty output)          54     54 Signal 54
        23    URG Urgent I/O condition          55     55 Signal 55
        24   XCPU CPU time limit exceeded       56     56 Signal 56
        25   XFSZ File size limit exceeded      57     57 Signal 57
        26 VTALRM Virtual timer expired         58     58 Signal 58
        27   PROF Profiling timer expired       59     59 Signal 59
        28  WINCH Window size changed           60     60 Signal 60
        29     IO I/O possible                  61     61 Signal 61
        30    PWR Power failure                 62     62 Signal 62
        31    SYS Bad system call               63     63 Signal 63
        32     32 Signal 32                     64     64 Signal 64
        */

        // 代碼跳轉(zhuǎn)錨點(diǎn)
        if (sigsetjmp(JUMP_ANCHOR, 1) != 0) {
            return -1;
        }

        // 注冊(cè)要捕捉的系統(tǒng)信號(hào)量
        struct sigaction sigact;
        struct sigaction old_action;
        sigaction(SIGABRT, NULL, &old_action);
        if (old_action.sa_handler != SIG_IGN) {
            sigset_t block_mask;
            sigemptyset(&block_mask);
            sigaddset(&block_mask, SIGABRT); // handler處理捕捉到的信號(hào)量時(shí)勾徽,需要阻塞的信號(hào)
            sigaddset(&block_mask, SIGSEGV); // handler處理捕捉到的信號(hào)量時(shí),需要阻塞的信號(hào)

            sigemptyset(&sigact.sa_mask);
            sigact.sa_flags = 0;
            sigact.sa_mask = block_mask;
            sigact.sa_handler = exception_handler;
            sigaction(SIGABRT, &sigact, NULL); // 注冊(cè)要捕捉的信號(hào)
            sigaction(SIGSEGV, &sigact, NULL); // 注冊(cè)要捕捉的信號(hào)
        }

        jint value = process(env, jobj, m, n);
        return value;
}

利用上面的兩種方法统扳,我們就可以有的放矢的處理JNI異常了喘帚,既可以在我們預(yù)測(cè)會(huì)發(fā)生異常的地方提前進(jìn)行異常檢測(cè)和處理,又可以全局添加崩潰捕獲咒钟,作為最后的防線吹由,這樣就可以告別JNI Crash問題了。

本文github源碼地址

參考:
https://www.gnu.org/software/libc/manual/pdf/libc.pdf

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末盯腌,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子陨瘩,更是在濱河造成了極大的恐慌腕够,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舌劳,死亡現(xiàn)場(chǎng)離奇詭異帚湘,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)甚淡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門大诸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人贯卦,你說我怎么就攤上這事资柔。” “怎么了撵割?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵贿堰,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我啡彬,道長(zhǎng)羹与,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任庶灿,我火速辦了婚禮纵搁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘往踢。我一直安慰自己腾誉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著妄辩,像睡著了一般惑灵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上眼耀,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天英支,我揣著相機(jī)與錄音,去河邊找鬼哮伟。 笑死干花,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的楞黄。 我是一名探鬼主播池凄,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼鬼廓!你這毒婦竟也來了肿仑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤碎税,失蹤者是張志新(化名)和其女友劉穎尤慰,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體雷蹂,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡伟端,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了匪煌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片责蝠。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖萎庭,靈堂內(nèi)的尸體忽然破棺而出霜医,到底是詐尸還是另有隱情,我是刑警寧澤驳规,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布支子,位于F島的核電站,受9級(jí)特大地震影響达舒,放射性物質(zhì)發(fā)生泄漏值朋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一巩搏、第九天 我趴在偏房一處隱蔽的房頂上張望昨登。 院中可真熱鬧,春花似錦贯底、人聲如沸丰辣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽笙什。三九已至飘哨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間琐凭,已是汗流浹背芽隆。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留统屈,地道東北人胚吁。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像愁憔,于是被迫代替她去往敵國(guó)和親腕扶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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