SegmentFault处理流程

本文将基于一个简单的用户态段错误问题,简单梳理下arm64平台SegmentFault处理流程。

1. Demo

1.1 运行环境

  • Linux + arm64平台
  • kernel 4.9
  • gcc version 6.3.1 20170404 (Linaro GCC 6.3-2017.05)

1.2 测试程序(el0_da.c)

#include <stddef.h>

int main(int argc, char *argv[]) {
  char *p = NULL;

  *p = 1; 

  return 0;
}

反汇编如下

0000000000000000 <main>:
   0:   d10083ff    sub sp, sp, #0x20
   4:   b9000fe0    str w0, [sp,#12]
   8:   f90003e1    str x1, [sp]
   c:   f9000fff    str xzr, [sp,#24]
  10:   f9400fe0    ldr x0, [sp,#24]
  14:   52800021    mov w1, #0x1                    // #1
  18:   39000001    strb    w1, [x0]
  1c:   52800000    mov w0, #0x0                    // #0
  20:   910083ff    add sp, sp, #0x20
  24:   d65f03c0    ret

1.3 运行结果

./run.sh: line 9:  1131 Segmentation fault      (core dumped) ./el0_da

dmesg打印的kenrel log如下

$ dmesg -c
[  720.577925] [1] el0_da[1131]: unhandled level 1 translation fault (11) at 0x00000000, esr 0x92000045
[  720.587064] [1] pgd = ffffffc274af0000
[  720.590821] [1] [00000000] *pgd=0000000000000000
[  720.595271] [1] , *pud=0000000000000000
[  720.599104] [1] 
[  720.600942] [1] 
[  720.602778] [1] CPU: 1 PID: 1131 Comm: el0_da Not tainted 4.9.38-timchip-v4.3.0-00361-gd5024dc9d6f5 #1
[  720.611989] [1] Hardware name: linux,dummy-virt (DT)
[  720.616951] [1] task: ffffffc26c9fb000 task.stack: ffffffc276cf8000
[  720.623216] [1] PC is at 0x5593dfa800
[  720.626876] [1] LR is at 0x7f7dd27364
[  720.630537] [1] pc : [<0000005593dfa800>] lr : [<0000007f7dd27364>] pstate: 60000000
[  720.638273] [1] sp : 0000007fcc5b8590
[  720.641932] [1] x29: 0000007fcc5b85b0 x28: 0000000000000000 
[  720.647611] [1] x27: 0000000000000000 x26: 0000000000000000 
[  720.653287] [1] x25: 0000000000000000 x24: 0000000000000000 
[  720.658963] [1] x23: 0000000000000000 x22: 0000000000000000 
[  720.664643] [1] x21: 0000005593dfa6a0 x20: 0000000000000000 
[  720.670319] [1] x19: 0000005593dfa810 x18: 0000000000040926 
[  720.675998] [1] x17: 0000005593e0b008 x16: 0000007f7dd27288 
[  720.681675] [1] x15: 000000000000080d x14: 0000000000000000 
[  720.687351] [1] x13: 0000007f7de80028 x12: 0000007f7de80030 
[  720.693030] [1] x11: 0000040000000000 x10: 0101010101010101 
[  720.698706] [1] x9 : 03ffffffffffffff x8 : ffffffffffffffff 
[  720.704385] [1] x7 : 0000040000000000 x6 : 0000000000000000 
[  720.710061] [1] x5 : 0000000000000000 x4 : 0000007fcc5b8608 
[  720.715740] [1] x3 : 0000005593dfa7e8 x2 : 0000007fcc5b86f8 
[  720.721416] [1] x1 : 0000000000000001 x0 : 0000000000000000 
[  720.727091] [1] 

2. 处理流程

2.1 page fault

  • 用户态进程访问了非法地址后, CPU的MMU无法完成虚拟地址到物理地址的转换,从而产生page fault异常。
  • 此后,由用户态切换到内核态

2.2 异常向量表

  • 源码位于arch/arm64/kernel/entry.S
  • 用户态触发的访问内存异常, 最终会进入到异常向量表的el0_sync

el0_sync如下

/*
 * EL0 mode handlers.
 */
        .align  6
el0_sync:
        kernel_entry 0
        mrs     x25, esr_el1                    // read the syndrome register
        lsr     x24, x25, #ESR_ELx_EC_SHIFT     // exception class
        cmp     x24, #ESR_ELx_EC_SVC64          // SVC in 64-bit state
        b.eq    el0_svc
        cmp     x24, #ESR_ELx_EC_DABT_LOW       // data abort in EL0
        b.eq    el0_da
        cmp     x24, #ESR_ELx_EC_IABT_LOW       // instruction abort in EL0
        b.eq    el0_ia
        cmp     x24, #ESR_ELx_EC_FP_ASIMD       // FP/ASIMD access
        b.eq    el0_fpsimd_acc
        cmp     x24, #ESR_ELx_EC_FP_EXC64       // FP/ASIMD exception
        b.eq    el0_fpsimd_exc
        cmp     x24, #ESR_ELx_EC_SYS64          // configurable trap
        b.eq    el0_sys
        cmp     x24, #ESR_ELx_EC_SP_ALIGN       // stack alignment exception
        b.eq    el0_sp_pc
        cmp     x24, #ESR_ELx_EC_PC_ALIGN       // pc alignment exception
        b.eq    el0_sp_pc
        cmp     x24, #ESR_ELx_EC_UNKNOWN        // unknown exception in EL0
        b.eq    el0_undef
        cmp     x24, #ESR_ELx_EC_BREAKPT_LOW    // debug exception in EL0
        b.ge    el0_dbg
        b       el0_inv

这里简单解释下

  • kernel_entry: 构造pt_regs相关的数据(包括通用目的寄存器,sp, pc等),保存到当前内核栈
  • esr_el1是异常诊断寄存器,用于存储跳转EL1的异常相关信息


    ESR

高6位是exception class, 用于标识当前异常的类型

根据前面的测试用例,esr值为0x92000045,则exception class= esr >> 26 = 0x24, 对应ESR_ELx_EC_DABT_LOW

#define ESR_ELx_EC_DABT_LOW     (0x24)

会跳到el0_da继续处理,el0_da的实现如下

el0_da:
    /*
     * Data abort handling
     */
    mrs x26, far_el1
    // enable interrupts before calling the main handler
    enable_dbg_and_irq
    ct_user_exit
    clear_address_tag x0, x26
    mov x1, x25
    mov x2, sp
    bl  do_mem_abort
    b   ret_to_user
el0_ia:

el0_da的操作

  • do_mem_abort()

    • far_el1是出错的内存地址,保存到x0
    • x25是esr_el1,保存到x1
    • sp是保存的struct pt_regs基地址,保存到x2
  • ret_to_user()

    • 调用kernel_exit 0, 最终返回用户态。

2.3 do_mem_abort

源码位于arch/arm64/mm/fault.c

/*
 * Dispatch a data abort to the relevant handler.
 */
asmlinkage void __exception do_mem_abort(unsigned long addr, unsigned int esr,
                     struct pt_regs *regs)
{
    const struct fault_info *inf = esr_to_fault_info(esr);
    struct siginfo info;

    if (!inf->fn(addr, esr, regs))
        return;

    pr_alert("Unhandled fault: %s (0x%08x) at 0x%016lx\n",
         inf->name, esr, addr);

    info.si_signo = inf->sig;
    info.si_errno = 0;
    info.si_code  = inf->code;
    info.si_addr  = (void __user *)addr;
    arm64_notify_die("", regs, &info, esr);
}

esr_to_fault_info()函数用于从esr的低6bit取出错误状态码DFSC(Data Fault Status Code)

DFSC 说明
000000 Address size fault, level 0 of translation or translation table base register
000001 Address size fault, level 1
000010 Address size fault, level 2
000011 Address size fault, level 3
000100 Translation fault, level 0
000101 Translation fault, level 1
000110 Translation fault, level 2
000111 Translation fault, level 3
001001 Access flag fault, level 1
001010 Access flag fault, level 2
001011 Access flag fault, level 3
001101 Permission fault, level 1
001110 Permission fault, level 2
001111 Permission fault, level 3
010000 Synchronous External abort, not on translation table walk
011000 Synchronous parity or ECC error on memory access, not on translation table walk
010100 Synchronous External abort, on translation table walk, level 0
010101 Synchronous External abort, on translation table walk, level 1
010110 Synchronous External abort, on translation table walk, level 2
010111 Synchronous External abort, on translation table walk, level 3
011100 Synchronous parity or ECC error on memory access on translation table walk, level 0
011101 Synchronous parity or ECC error on memory access on translation table walk, level 1
011110 Synchronous parity or ECC error on memory access on translation table walk, level 2
011111 Synchronous parity or ECC error on memory access on translation table walk, level 3
100001 Alignment fault
110000 TLB conflict abort
110001 Unsupported atomic hardware update fault, if the implementation includes ARMv8.1-TTHM. Otherwise reserved.
110100 IMPLEMENTATION DEFINED fault (Lockdown)
110101 IMPLEMENTATION DEFINED fault (Unsupported Exclusive or Atomic access)
111101 Section Domain Fault, used only for faults reported in the PAR_EL1
111110 Page Domain Fault, used only for faults reported in the PAR_EL1

而fault_info[]是一个struct fault_info结构体数组,对应这64种错误状态码的处理

static const struct fault_info fault_info[] = { 
        { do_bad,               SIGBUS,  0,             "ttbr address size fault"       },
        { do_bad,               SIGBUS,  0,             "level 1 address size fault"    },  
        { do_bad,               SIGBUS,  0,             "level 2 address size fault"    },  
        { do_bad,               SIGBUS,  0,             "level 3 address size fault"    },  
        { do_translation_fault, SIGSEGV, SEGV_MAPERR,   "level 0 translation fault"     },  
        { do_translation_fault, SIGSEGV, SEGV_MAPERR,   "level 1 translation fault"     },  
        { do_translation_fault, SIGSEGV, SEGV_MAPERR,   "level 2 translation fault"     },  
        { do_page_fault,        SIGSEGV, SEGV_MAPERR,   "level 3 translation fault"     },  
        { do_bad,               SIGBUS,  0,             "unknown 8"                     },
        { do_page_fault,        SIGSEGV, SEGV_ACCERR,   "level 1 access flag fault"     },  
        { do_page_fault,        SIGSEGV, SEGV_ACCERR,   "level 2 access flag fault"     },  
        { do_page_fault,        SIGSEGV, SEGV_ACCERR,   "level 3 access flag fault"     },  
        { do_bad,               SIGBUS,  0,             "unknown 12"                    },
        { do_page_fault,        SIGSEGV, SEGV_ACCERR,   "level 1 permission fault"      },  
        { do_page_fault,        SIGSEGV, SEGV_ACCERR,   "level 2 permission fault"      },  
        { do_page_fault,        SIGSEGV, SEGV_ACCERR,   "level 3 permission fault"      },  
        { do_bad,               SIGBUS,  0,             "synchronous external abort"    },  
        { do_bad,               SIGBUS,  0,             "unknown 17"                    },
        { do_bad,               SIGBUS,  0,             "unknown 18"                    },
        { do_bad,               SIGBUS,  0,             "unknown 19"                    },
        { do_bad,               SIGBUS,  0,             "synchronous external abort (translation table walk)" },
        { do_bad,               SIGBUS,  0,             "synchronous external abort (translation table walk)" },
        { do_bad,               SIGBUS,  0,             "synchronous external abort (translation table walk)" },
        { do_bad,               SIGBUS,  0,             "synchronous external abort (translation table walk)" },
        { do_bad,               SIGBUS,  0,             "synchronous parity error"      },  
        { do_bad,               SIGBUS,  0,             "unknown 25"                    },
        { do_bad,               SIGBUS,  0,             "unknown 26"                    },
        { do_bad,               SIGBUS,  0,             "unknown 27"                    },
        { do_bad,               SIGBUS,  0,             "synchronous parity error (translation table walk)" },
        { do_bad,               SIGBUS,  0,             "synchronous parity error (translation table walk)" },
        { do_bad,               SIGBUS,  0,             "synchronous parity error (translation table walk)" },
        { do_bad,               SIGBUS,  0,             "synchronous parity error (translation table walk)" },
        { do_bad,               SIGBUS,  0,             "unknown 32"                    },
        { do_alignment_fault,   SIGBUS,  BUS_ADRALN,    "alignment fault"               },
        { do_bad,               SIGBUS,  0,             "unknown 34"                    },
        { do_bad,               SIGBUS,  0,             "unknown 35"                    },
        { do_bad,               SIGBUS,  0,             "unknown 36"                    },
        { do_bad,               SIGBUS,  0,             "unknown 37"                    },
        { do_bad,               SIGBUS,  0,             "unknown 38"                    },
        { do_bad,               SIGBUS,  0,             "unknown 39"                    },
        { do_bad,               SIGBUS,  0,             "unknown 40"                    },
        { do_bad,               SIGBUS,  0,             "unknown 41"                    },
        { do_bad,               SIGBUS,  0,             "unknown 42"                    },
        { do_bad,               SIGBUS,  0,             "unknown 43"                    },
        { do_bad,               SIGBUS,  0,             "unknown 44"                    },
        { do_bad,               SIGBUS,  0,             "unknown 45"                    },
        { do_bad,               SIGBUS,  0,             "unknown 46"                    },
        { do_bad,               SIGBUS,  0,             "unknown 47"                    },
        { do_bad,               SIGBUS,  0,             "TLB conflict abort"            },
        { do_bad,               SIGBUS,  0,             "unknown 49"                    },
        { do_bad,               SIGBUS,  0,             "unknown 50"                    },
        { do_bad,               SIGBUS,  0,             "unknown 51"                    },
        { do_bad,               SIGBUS,  0,             "implementation fault (lockdown abort)" },
        { do_bad,               SIGBUS,  0,             "implementation fault (unsupported exclusive)" },
        { do_bad,               SIGBUS,  0,             "unknown 54"                    },
        { do_bad,               SIGBUS,  0,             "unknown 55"                    },
        { do_bad,               SIGBUS,  0,             "unknown 56"                    },
        { do_bad,               SIGBUS,  0,             "unknown 57"                    },
        { do_bad,               SIGBUS,  0,             "unknown 58"                    },
        { do_bad,               SIGBUS,  0,             "unknown 59"                    },
        { do_bad,               SIGBUS,  0,             "unknown 60"                    },
        { do_bad,               SIGBUS,  0,             "section domain fault"          },
        { do_bad,               SIGBUS,  0,             "page domain fault"             },
        { do_bad,               SIGBUS,  0,             "unknown 63"                    },
};

dfsc = esr & 0x3f = 0x92000045 & 0x3f = 0x5, 对应fault_info[]中的第5个元素"level 1 translation fault",下一步会跳到do_translation_fault()处理。

2.4 do_translation_fault

/*
 * First Level Translation Fault Handler
 *
 * We enter here because the first level page table doesn't contain a valid
 * entry for the address.
 *
 * If the address is in kernel space (>= TASK_SIZE), then we are probably
 * faulting in the vmalloc() area.
 *
 * If the init_task's first level page tables contains the relevant entry, we
 * copy the it to this task.  If not, we send the process a signal, fixup the
 * exception, or oops the kernel.
 *
 * NOTE! We MUST NOT take any locks for this case. We may be in an interrupt
 * or a critical region, and should only copy the information from the master
 * page table, nothing more.
 */
static int __kprobes do_translation_fault(unsigned long addr,
                      unsigned int esr,
                      struct pt_regs *regs)
{
    if (addr < TASK_SIZE)
        return do_page_fault(addr, esr, regs);

    do_bad_area(addr, esr, regs);
    return 0;
}

这里会跳到do_page_fault()

2.5 do_page_fault

do_page_fault()主要会调用

  • __do_page_fault()
  • __do_user_fault()

__do_page_fault()的实现如下

static int __do_page_fault(struct mm_struct *mm, unsigned long addr,
                           unsigned int mm_flags, unsigned long vm_flags,
                           struct task_struct *tsk)
{
        struct vm_area_struct *vma;
        int fault;

        vma = find_vma(mm, addr);
        fault = VM_FAULT_BADMAP;
        if (unlikely(!vma))
                goto out;
        if (unlikely(vma->vm_start > addr))
                goto check_stack;

        /*
         * Ok, we have a good vm_area for this memory access, so we can handle
         * it.
         */
good_area:
        /*
         * Check that the permissions on the VMA allow for the fault which
         * occurred.
         */
        if (!(vma->vm_flags & vm_flags)) {
                fault = VM_FAULT_BADACCESS;
                goto out;
        }

        return handle_mm_fault(vma, addr & PAGE_MASK, mm_flags);

check_stack:
        if (vma->vm_flags & VM_GROWSDOWN && !expand_stack(vma, addr))
                goto good_area;
out:
        return fault;
}

__do_page_fault()这里, 没有找到相应的vma, 则会直接返回。

前面的page fault无法处理后, 若是用户态page fault,最终会走到__do_user_fault()

2.6 __do_user_fault

static void __do_user_fault(struct task_struct *tsk, unsigned long addr,
                            unsigned int esr, unsigned int sig, int code,
                            struct pt_regs *regs)
{
        struct siginfo si;
        const struct fault_info *inf;
                
        if (unhandled_signal(tsk, sig) && show_unhandled_signals_ratelimited()) {
                inf = esr_to_fault_info(esr);
                pr_info("%s[%d]: unhandled %s (%d) at 0x%08lx, esr 0x%03x\n",
                        tsk->comm, task_pid_nr(tsk), inf->name, sig,
                        addr, esr);
                show_pte(tsk->mm, addr);
                show_regs(regs);
        }

        tsk->thread.fault_address = addr;
        tsk->thread.fault_code = esr;
        si.si_signo = sig;
        si.si_errno = 0;
        si.si_code = code;
        si.si_addr = (void __user *)addr;
        force_sig_info(sig, &si, tsk);
}

__do_user_fault()主要做几件事:

2.6.1 打印出错进程信息

el0_da[1131]: unhandled level 1 translation fault (11) at 0x00000000, esr 0x92000045

2.6.2 show_pte()

  • 打印pgd/pud/pmd等信息
/*
 * Dump out the page tables associated with 'addr' in mm 'mm'.
 */
void show_pte(struct mm_struct *mm, unsigned long addr)
{
        pgd_t *pgd;

        if (!mm)
                mm = &init_mm;

        pr_alert("pgd = %p\n", mm->pgd);
        pgd = pgd_offset(mm, addr);
        pr_alert("[%08lx] *pgd=%016llx", addr, pgd_val(*pgd));

        do {
                pud_t *pud;
                pmd_t *pmd;
                pte_t *pte;

                if (pgd_none(*pgd) || pgd_bad(*pgd))
                        break;

                pud = pud_offset(pgd, addr);
                printk(", *pud=%016llx", pud_val(*pud));
                if (pud_none(*pud) || pud_bad(*pud))
                        break;

                pmd = pmd_offset(pud, addr);
                printk(", *pmd=%016llx", pmd_val(*pmd));
                if (pmd_none(*pmd) || pmd_bad(*pmd))
                        break;

                pte = pte_offset_map(pmd, addr);
                printk(", *pte=%016llx", pte_val(*pte));
                pte_unmap(pte);
        } while(0);

        printk("\n");
}

2.6.3 show_regs()

  • 源码位于arch/arm64/kernel/process.c
  • 打印PC/LR/SP/通用目的寄存器等
void __show_regs(struct pt_regs *regs)
{
        int i, top_reg;
        u64 lr, sp;

        if (compat_user_mode(regs)) {
                lr = regs->compat_lr;
                sp = regs->compat_sp;
                top_reg = 12;
        } else {
                lr = regs->regs[30];
                sp = regs->sp;
                top_reg = 29;
        }

        show_regs_print_info(KERN_DEFAULT);
        print_symbol("PC is at %s\n", instruction_pointer(regs));
        print_symbol("LR is at %s\n", lr);
        printk("pc : [<%016llx>] lr : [<%016llx>] pstate: %08llx\n",
               regs->pc, lr, regs->pstate);
        printk("sp : %016llx\n", sp);

        i = top_reg;

        while (i >= 0) {
                printk("x%-2d: %016llx ", i, regs->regs[i]);
                i--;

                if (i % 2 == 0) {
                        pr_cont("x%-2d: %016llx ", i, regs->regs[i]);
                        i--;
                }

                pr_cont("\n");
        }
        printk("\n");
}

void show_regs(struct pt_regs * regs)
{
        printk("\n");
        __show_regs(regs);
}

show_regs_print_info()相关

  • 源码位于kernel/printk/printk.c
  • 用于打印通用的debug信息
void dump_stack_print_info(const char *log_lvl)
{
        printk("%sCPU: %d PID: %d Comm: %.20s %s %s %.*s\n",
               log_lvl, raw_smp_processor_id(), current->pid, current->comm,
               print_tainted(), init_utsname()->release,
               (int)strcspn(init_utsname()->version, " "),
               init_utsname()->version);

        if (dump_stack_arch_desc_str[0] != '\0')
                printk("%sHardware name: %s\n",
                       log_lvl, dump_stack_arch_desc_str);

        print_worker_info(log_lvl, current);
}

/**
 * show_regs_print_info - print generic debug info for show_regs()
 * @log_lvl: log level
 *
 * show_regs() implementations can use this function to print out generic
 * debug information.
 */
void show_regs_print_info(const char *log_lvl)
{
        dump_stack_print_info(log_lvl);

        printk("%stask: %p task.stack: %p\n",
               log_lvl, current, task_stack_page(current));
}

2.6.4 force_sig_info()

  • 源码位于source/kernel/signal.c
  • 用于向进程发送信号信息
/*
 * Force a signal that the process can't ignore: if necessary
 * we unblock the signal and change any SIG_IGN to SIG_DFL.
 *
 * Note: If we unblock the signal, we always reset it to SIG_DFL,
 * since we do not want to have a signal handler that was blocked
 * be invoked when user space had explicitly blocked it.
 *
 * We don't want to have recursive SIGSEGV's etc, for example,
 * that is why we also clear SIGNAL_UNKILLABLE.
 */
int
force_sig_info(int sig, struct siginfo *info, struct task_struct *t)
{
    unsigned long int flags;
    int ret, blocked, ignored;
    struct k_sigaction *action;

    spin_lock_irqsave(&t->sighand->siglock, flags);
    action = &t->sighand->action[sig-1];
    ignored = action->sa.sa_handler == SIG_IGN;
    blocked = sigismember(&t->blocked, sig);
    if (blocked || ignored) {
        action->sa.sa_handler = SIG_DFL;
        if (blocked) {
            sigdelset(&t->blocked, sig);
            recalc_sigpending_and_wake(t);
        }
    }
    if (action->sa.sa_handler == SIG_DFL)
        t->signal->flags &= ~SIGNAL_UNKILLABLE;
    ret = specific_send_sig_info(sig, info, t);
    spin_unlock_irqrestore(&t->sighand->siglock, flags);

    return ret;
}

static int
specific_send_sig_info(int sig, struct siginfo *info, struct task_struct *t)
{
    return send_signal(sig, info, t, 0);
}

3. 总结

  • 本文通过简单例子,分析SegmentFault的处理流程
  • 针对SegmentFault问题,可以借助gdb在线分析进程或离线分core dump等,来定位具体出错的地方。

程序员自我修养(ID: dumphex)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容