Golang 程序啟動(dòng)流程分析

本文使用 golang 1.17 代碼棺棵,如有任何問(wèn)題良蛮,還望指出哪怔。

Golang 代碼被操作系統(tǒng)運(yùn)行起來(lái)的流程

一灶伊、編譯

go 源代碼首先要通過(guò) go build 編譯為可執(zhí)行文件,在 linux 平臺(tái)上為 ELF 格式的可執(zhí)行文件肚逸,編譯階段會(huì)經(jīng)過(guò)編譯器爷辙、匯編器彬坏、鏈接器三個(gè)過(guò)程最終生成可執(zhí)行文件。

  • 1膝晾、編譯器:*.go 源碼通過(guò) go 編譯器生成為 *.s 的 plan9 匯編代碼栓始,Go 編譯器入口是 compile/internal/gc/main.go 文件的 main 函數(shù);
  • 2血当、匯編器:通過(guò) go 匯編器將編譯器生成的 *.s 匯編語(yǔ)言轉(zhuǎn)換為機(jī)器代碼幻赚,并寫(xiě)出最終的目標(biāo)程序 *.o 文件,src/cmd/internal/obj 包實(shí)現(xiàn)了go匯編器歹颓;
  • 3坯屿、鏈接器:匯編器生成的一個(gè)個(gè) *.o 目標(biāo)文件通過(guò)鏈接處理得到最終的可執(zhí)行程序,src/cmd/link/internal/ld 包實(shí)現(xiàn)了鏈接器巍扛;

二、運(yùn)行

go 源碼通過(guò)上述幾個(gè)步驟生成可執(zhí)行文件后乏德,二進(jìn)制文件在被操作系統(tǒng)加載起來(lái)運(yùn)行時(shí)會(huì)經(jīng)過(guò)如下幾個(gè)階段:

  • 1撤奸、從磁盤(pán)上把可執(zhí)行程序讀入內(nèi)存;

  • 2喊括、創(chuàng)建進(jìn)程和主線(xiàn)程胧瓜;

  • 3、為主線(xiàn)程分配椫J玻空間府喳;

  • 4、把由用戶(hù)在命令行輸入的參數(shù)拷貝到主線(xiàn)程的棧蘑拯;

  • 5钝满、把主線(xiàn)程放入操作系統(tǒng)的運(yùn)行隊(duì)列等待被調(diào)度執(zhí)起來(lái)運(yùn)行;

Golang 程序啟動(dòng)流程分析

1申窘、通過(guò) gdb 調(diào)試分析程序啟動(dòng)流程

此處以一個(gè)簡(jiǎn)單的 go 程序通過(guò)單步調(diào)試來(lái)分析其啟動(dòng)過(guò)程的流程:

main.go

package main

import "fmt"

func main() {
    fmt.Println("hello world")
}

編譯該程序并使用 gdb 進(jìn)行調(diào)試弯蚜。使用 gdb 調(diào)試時(shí)首先在程序入口處設(shè)置一個(gè)斷點(diǎn),然后進(jìn)行單步調(diào)試即可看到該程序啟動(dòng)過(guò)程中的代碼執(zhí)行流程剃法。

$ go build -gcflags "-N -l" -o main main.go

$ gdb ./main

(gdb) info files
Symbols from "/home/gosoon/main".
Local exec file:
    `/home/gosoon/main', file type elf64-x86-64.
    Entry point: 0x465860
    0x0000000000401000 - 0x0000000000497893 is .text
    0x0000000000498000 - 0x00000000004dbb65 is .rodata
    0x00000000004dbd00 - 0x00000000004dc42c is .typelink
    0x00000000004dc440 - 0x00000000004dc490 is .itablink
    0x00000000004dc490 - 0x00000000004dc490 is .gosymtab
    0x00000000004dc4a0 - 0x0000000000534b90 is .gopclntab
    0x0000000000535000 - 0x0000000000535020 is .go.buildinfo
    0x0000000000535020 - 0x00000000005432e4 is .noptrdata
    0x0000000000543300 - 0x000000000054aa70 is .data
    0x000000000054aa80 - 0x00000000005781f0 is .bss
    0x0000000000578200 - 0x000000000057d510 is .noptrbss
    0x0000000000400f9c - 0x0000000000401000 is .note.go.buildid
(gdb) b *0x465860
Breakpoint 1 at 0x465860: file /home/gosoon/golang/go/src/runtime/rt0_linux_amd64.s, line 8.
(gdb) r
Starting program: /home/gaofeilei/./main

Breakpoint 1, _rt0_amd64_linux () at /home/gaofeilei/golang/go/src/runtime/rt0_linux_amd64.s:8
8       JMP _rt0_amd64(SB)
(gdb) n
_rt0_amd64 () at /home/gaofeilei/golang/go/src/runtime/asm_amd64.s:15
15      MOVQ    0(SP), DI   // argc
(gdb) n
16      LEAQ    8(SP), SI   // argv
(gdb) n
17      JMP runtime·rt0_go(SB)
(gdb) n
runtime.rt0_go () at /home/gaofeilei/golang/go/src/runtime/asm_amd64.s:91
91      MOVQ    DI, AX      // argc
......
231     CALL    runtime·mstart(SB)
(gdb) n
hello world
[Inferior 1 (process 39563) exited normally]

通過(guò)單步調(diào)試可以看到程序入口函數(shù)在 runtime/rt0_linux_amd64.s 文件中的第 8 行碎捺,最終會(huì)執(zhí)行 CALL runtime·mstart(SB) 指令后輸出 “hello world” 然后程序就退出了。

啟動(dòng)流程流程中的函數(shù)調(diào)用如下所示:

rt0_linux_amd64.s -->_rt0_amd64 --> rt0_go-->runtime·settls -->runtime·check-->runtime·args-->runtime·osinit-->runtime·schedinit-->runtime·newproc-->runtime·mstart

2贷洲、golang 啟動(dòng)流程分析

上節(jié)通過(guò)gdb調(diào)試已經(jīng)看到了 golang 程序在啟動(dòng)過(guò)程中會(huì)執(zhí)行一系列的匯編指令收厨,本節(jié)會(huì)具體分析啟動(dòng)程序過(guò)程中每條指令的含義,了解了這些才能明白 golang 程序在啟動(dòng)過(guò)程中所執(zhí)行的操作优构。

src/runtime/rt0_linux_amd64.s

#include "textflag.h"

TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
    JMP _rt0_amd64(SB)

TEXT _rt0_amd64_linux_lib(SB),NOSPLIT,$0
    JMP _rt0_amd64_lib(SB)

首先執(zhí)行的第8行即 JMP _rt0_amd64诵叁,此處在 amd64 平臺(tái)下運(yùn)行,_rt0_amd64 函數(shù)所在的文件為 src/runtime/asm_amd64.s俩块。

TEXT _rt0_amd64(SB),NOSPLIT,$-8
    // 處理 argc 和 argv 參數(shù)黎休,argc 是指命令行輸入?yún)?shù)的個(gè)數(shù)浓领,argv 存儲(chǔ)了所有的命令行參數(shù)
    MOVQ    0(SP), DI   // argc
    // argv 為指針類(lèi)型
    LEAQ    8(SP), SI   // argv
    JMP runtime·rt0_go(SB)

_rt0_amd64 函數(shù)中將 argc 和 argv 兩個(gè)參數(shù)保存到 DI 和 SI 寄存器后跳轉(zhuǎn)到了 rt0_go 函數(shù),rt0_go 函數(shù)的主要作用:

  • 1势腮、將 argc联贩、argv 參數(shù)拷貝到主線(xiàn)程棧上;
  • 2捎拯、初始化全局變量 g0泪幌,為 g0 在主線(xiàn)程棧上分配大約 64K 棧空間署照,并設(shè)置 g0 的stackguard0祸泪,stackguard1,stack 三個(gè)字段建芙;
  • 3没隘、執(zhí)行 CPUID 指令,探測(cè) CPU 信息禁荸;
  • 4右蒲、執(zhí)行 nocpuinfo 代碼塊判斷是否需要初始化 cgo;
  • 5赶熟、執(zhí)行 needtls 代碼塊瑰妄,初始化 tls 和 m0;
  • 6映砖、執(zhí)行 ok 代碼塊间坐,首先將 m0 和 g0 綁定,然后調(diào)用 runtime·args 函數(shù)處理進(jìn)程參數(shù)和環(huán)境變量邑退,調(diào)用 runtime·osinit 函數(shù)初始化 cpu 數(shù)量竹宋,調(diào)用 runtime·schedinit 初始化調(diào)度器,調(diào)用 runtime·newproc 創(chuàng)建第一個(gè) goroutine 執(zhí)行 main 函數(shù)瓜饥,調(diào)用 runtime·mstart 啟動(dòng)主線(xiàn)程逝撬,主線(xiàn)程會(huì)執(zhí)行第一個(gè) goroutine 來(lái)運(yùn)行 main 函數(shù),此處會(huì)阻塞住直到進(jìn)程退出乓土;
TEXT runtime·rt0_go(SB),NOSPLIT|TOPFRAME,$0
    // 處理命令行參數(shù)的代碼
    MOVQ    DI, AX      // AX = argc
    MOVQ    SI, BX      // BX = argv
    // 將棧擴(kuò)大39字節(jié)宪潮,此處為什么擴(kuò)大39字節(jié)暫時(shí)還沒(méi)有搞清楚
    SUBQ    $(4*8+7), SP
    ANDQ    $~15, SP    // 調(diào)整為 16 字節(jié)對(duì)齊
    MOVQ    AX, 16(SP)  //argc放在SP + 16字節(jié)處
    MOVQ    BX, 24(SP)  //argv放在SP + 24字節(jié)處

    // 開(kāi)始初始化 g0,runtime·g0 是一個(gè)全局變量趣苏,變量在 src/runtime/proc.go 中定義狡相,全局變量會(huì)保存在進(jìn)程內(nèi)存空間的數(shù)據(jù)區(qū),下文會(huì)介紹查看 elf 二進(jìn)制文件中的代碼數(shù)據(jù)和全局變量的方法
    // g0 的棧是從進(jìn)程棧內(nèi)存區(qū)進(jìn)行分配的食磕,g0 占用了大約 64k 大小尽棕。
    MOVQ    $runtime·g0(SB), DI    // g0 的地址放入 DI 寄存器
    LEAQ    (-64*1024+104)(SP), BX // BX = SP - 64*1024 + 104

    // 開(kāi)始初始化 g0 對(duì)象的 stackguard0,stackguard1,stack 這三個(gè)字段
    MOVQ    BX, g_stackguard0(DI) // g0.stackguard0 = SP - 64*1024 + 104
    MOVQ    BX, g_stackguard1(DI) // g0.stackguard1 = SP - 64*1024 + 104
    MOVQ    BX, (g_stack+stack_lo)(DI) // g0.stack.lo = SP - 64*1024 + 104
    MOVQ    SP, (g_stack+stack_hi)(DI) // g0.stack.hi = SP

執(zhí)行完以上指令后,進(jìn)程內(nèi)存空間布局如下所示:

然后開(kāi)始執(zhí)行獲取 cpu 信息的指令以及與 cgo 初始化相關(guān)的彬伦,此段代碼暫時(shí)可以不用關(guān)注滔悉。

    // 執(zhí)行CPUID指令伊诵,嘗試獲取CPU信息,探測(cè) CPU 和 指令集的代碼
    MOVL    $0, AX
    CPUID
    MOVL    AX, SI
    CMPL    AX, $0
    JE  nocpuinfo

    // Figure out how to serialize RDTSC.
    // On Intel processors LFENCE is enough. AMD requires MFENCE.
    // Don't know about the rest, so let's do MFENCE.
    CMPL    BX, $0x756E6547  // "Genu"
    JNE notintel
    CMPL    DX, $0x49656E69  // "ineI"
    JNE notintel
    CMPL    CX, $0x6C65746E  // "ntel"
    JNE notintel
    MOVB    $1, runtime·isIntel(SB)
    MOVB    $1, runtime·lfenceBeforeRdtsc(SB)
notintel:

    // Load EAX=1 cpuid flags
    MOVL    $1, AX
    CPUID
    MOVL    AX, runtime·processorVersionInfo(SB)

nocpuinfo:
    // cgo 初始化相關(guān)回官,_cgo_init 為全局變量
    MOVQ    _cgo_init(SB), AX
    // 檢查 AX 是否為 0
    TESTQ   AX, AX
    // 跳轉(zhuǎn)到 needtls
    JZ  needtls
    // arg 1: g0, already in DI
    MOVQ    $setg_gcc<>(SB), SI // arg 2: setg_gcc

    CALL    AX

    // 如果開(kāi)啟了 CGO 特性曹宴,則會(huì)修改 g0 的部分字段
    MOVQ    $runtime·g0(SB), CX
    MOVQ    (g_stack+stack_lo)(CX), AX
    ADDQ    $const__StackGuard, AX
    MOVQ    AX, g_stackguard0(CX)
    MOVQ    AX, g_stackguard1(CX)

下面開(kāi)始執(zhí)行 needtls 代碼塊,初始化 tls 和 m0歉提,tls 為線(xiàn)程本地存儲(chǔ)笛坦,在 golang 程序運(yùn)行過(guò)程中,每個(gè) m 都需要和一個(gè)工作線(xiàn)程關(guān)聯(lián)苔巨,那么工作線(xiàn)程如何知道其關(guān)聯(lián)的 m版扩,此時(shí)就會(huì)用到線(xiàn)程本地存儲(chǔ),線(xiàn)程本地存儲(chǔ)就是線(xiàn)程私有的全局變量侄泽,通過(guò)線(xiàn)程本地存儲(chǔ)可以為每個(gè)線(xiàn)程初始化一個(gè)私有的全局變量 m礁芦,然后就可以在每個(gè)工作線(xiàn)程中都使用相同的全局變量名來(lái)訪(fǎng)問(wèn)不同的 m 結(jié)構(gòu)體對(duì)象。后面會(huì)分析到其實(shí)每個(gè)工作線(xiàn)程 m 在剛剛被創(chuàng)建出來(lái)進(jìn)入調(diào)度循環(huán)之前就利用線(xiàn)程本地存儲(chǔ)機(jī)制為該工作線(xiàn)程實(shí)現(xiàn)了一個(gè)指向 m 結(jié)構(gòu)體實(shí)例對(duì)象的私有全局變量悼尾。

在后面代碼分析中宴偿,會(huì)經(jīng)常看到調(diào)用 getg 函數(shù)诀豁,getg 函數(shù)會(huì)從線(xiàn)程本地存儲(chǔ)中獲取當(dāng)前正在運(yùn)行的 g,這里獲取出來(lái)的 m 關(guān)聯(lián)的 g0窥妇。

tls 地址會(huì)寫(xiě)到 m0 中舷胜,而 m0 會(huì)和 g0 綁定,所以可以直接從 tls 中獲取到 g0活翩。

// 下面開(kāi)始初始化tls(thread local storage烹骨,線(xiàn)程本地存儲(chǔ)),設(shè)置 m0 為線(xiàn)程私有變量材泄,將 m0 綁定到主線(xiàn)程
needtls:
    LEAQ    runtime·m0+m_tls(SB), DI  //DI = &m0.tls沮焕,取m0的tls成員的地址到DI寄存器

    // 調(diào)用 runtime·settls 函數(shù)設(shè)置線(xiàn)程本地存儲(chǔ),runtime·settls 函數(shù)的參數(shù)在 DI 寄存器中
    // 在 runtime·settls 函數(shù)中將 m0.tls[1] 的地址設(shè)置為 tls 的地址
    // runtime·settls 函數(shù)在 runtime/sys_linux_amd64.s#599
    CALL    runtime·settls(SB)

    // 此處是在驗(yàn)證本地存儲(chǔ)是否可以正常工作拉宗,確保值正確寫(xiě)入了 m0.tls峦树,
    // 如果有問(wèn)題則 abort 退出程序
    // get_tls 是宏,位于 runtime/go_tls.h
    get_tls(BX)                      // 將 tls 的地址放入 BX 中,即 BX = &m0.tls[1]
    MOVQ    $0x123, g(BX)  // BX = 0x123旦事,即 m0.tls[0] = 0x123
    MOVQ    runtime·m0+m_tls(SB), AX    // AX = m0.tls[0]
    CMPQ    AX, $0x123
    JEQ 2(PC)                                   // 如果相等則向后跳轉(zhuǎn)兩條指令即到 ok 代碼塊
    CALL    runtime·abort(SB)   // 使用 INT 指令執(zhí)行中斷

繼續(xù)執(zhí)行 ok 代碼塊魁巩,主要邏輯為:

  • 將 m0 和 g0 進(jìn)行綁定,啟動(dòng)主線(xiàn)程姐浮;
  • 調(diào)用 runtime·osinit 函數(shù)用來(lái)初始化 cpu 數(shù)量谷遂,調(diào)度器初始化時(shí)需要知道當(dāng)前系統(tǒng)有多少個(gè)CPU核;
  • 調(diào)用 runtime·schedinit 函數(shù)會(huì)初始化m0和p對(duì)象卖鲤,還設(shè)置了全局變量 sched 的 maxmcount 成員為10000肾扰,限制最多可以創(chuàng)建10000個(gè)操作系統(tǒng)線(xiàn)程出來(lái)工作畴嘶;
  • 調(diào)用 runtime·newproc 為main 函數(shù)創(chuàng)建 goroutine;
  • 調(diào)用 runtime·mstart 啟動(dòng)主線(xiàn)程集晚,執(zhí)行 main 函數(shù)窗悯;
// 首先將 g0 地址保存在 tls 中,即 m0.tls[0] = &g0甩恼,然后將 m0 和 g0 綁定
// 即 m0.g0 = g0, g0.m = m0
ok:
    get_tls(BX)                             // 獲取tls地址到BX寄存器蟀瞧,即 BX = m0.tls[0]
    LEAQ    runtime·g0(SB), CX  // CX = &g0
    MOVQ    CX, g(BX)                 // m0.tls[0]=&g0
    LEAQ    runtime·m0(SB), AX  // AX = &m0

    MOVQ    CX, m_g0(AX)  // m0.g0 = g0
    MOVQ    AX, g_m(CX)   // g0.m = m0

    CLD             // convention is D is always left cleared
    // check 函數(shù)檢查了各種類(lèi)型以及類(lèi)型轉(zhuǎn)換是否有問(wèn)題,位于 runtime/runtime1.go#137 中
    CALL    runtime·check(SB)

    // 將 argc 和 argv 移動(dòng)到 SP+0 和 SP+8 的位置
    // 此處是為了將 argc 和 argv 作為 runtime·args 函數(shù)的參數(shù)
    MOVL    16(SP), AX
    MOVL    AX, 0(SP)
    MOVQ    24(SP), AX
    MOVQ    AX, 8(SP)

    // args 函數(shù)會(huì)從棧中讀取參數(shù)和環(huán)境變量等進(jìn)行處理
    // args 函數(shù)位于 runtime/runtime1.go#61
    CALL    runtime·args(SB)

    // osinit 函數(shù)用來(lái)初始化 cpu 數(shù)量条摸,函數(shù)位于 runtime/os_linux.go#301
    CALL    runtime·osinit(SB)
    // schedinit 函數(shù)用來(lái)初始化調(diào)度器悦污,函數(shù)位于 runtime/proc.go#654
    CALL    runtime·schedinit(SB)

    // 創(chuàng)建第一個(gè) goroutine 執(zhí)行 runtime.main 函數(shù)。獲取 runtime.main 的地址钉蒲,調(diào)用 newproc 創(chuàng)建 g
    MOVQ    $runtime·mainPC(SB), AX
    PUSHQ   AX            // runtime.main 作為 newproc 的第二個(gè)參數(shù)入棧
    PUSHQ   $0            // newproc 的第一個(gè)參數(shù)入棧切端,該參數(shù)表示runtime.main函數(shù)需要的參數(shù)大小,runtime.main沒(méi)有參數(shù)顷啼,所以這里是0

    // newproc 創(chuàng)建一個(gè)新的 goroutine 并放置到等待隊(duì)列里踏枣,該 goroutine 會(huì)執(zhí)行runtime.main 函數(shù), 函數(shù)位于 runtime/proc.go#4250
    CALL    runtime·newproc(SB)
    // 彈出棧頂?shù)臄?shù)據(jù)
    POPQ    AX
    POPQ    AX

    // mstart 函數(shù)會(huì)啟動(dòng)主線(xiàn)程進(jìn)入調(diào)度循環(huán)钙蒙,然后運(yùn)行剛剛創(chuàng)建的 goroutine茵瀑,mstart 會(huì)阻塞住,除非函數(shù)退出躬厌,mstart 函數(shù)位于 runtime/proc.go#1328
    CALL    runtime·mstart(SB)

    CALL    runtime·abort(SB)   // mstart should never return
    RET

    // Prevent dead-code elimination of debugCallV2, which is
    // intended to be called by debuggers.
    MOVQ    $runtime·debugCallV2<ABIInternal>(SB), AX
    RET

此時(shí)進(jìn)程內(nèi)存空間布局如下所示:

查看 ELF 二進(jìn)制文件結(jié)構(gòu)

可以通過(guò) readelf 命令查看 ELF 二進(jìn)制文件的結(jié)構(gòu)马昨,可以看到二進(jìn)制文件中代碼區(qū)和數(shù)據(jù)區(qū)的內(nèi)容,全局變量保存在數(shù)據(jù)區(qū)扛施,函數(shù)保存在代碼區(qū)鸿捧。

$ readelf -s main | grep runtime.g0
  1765: 000000000054b3a0   376 OBJECT  GLOBAL DEFAULT   11 runtime.g0

// _cgo_init 為全局變量
$ readelf -s main | grep -i _cgo_init
  2159: 000000000054aa88     8 OBJECT  GLOBAL DEFAULT   11 _cgo_init

總結(jié)

本文主要介紹 Golang 程序啟動(dòng)流程中的關(guān)鍵代碼,啟動(dòng)過(guò)程的主要代碼是通過(guò) Plan9 匯編編寫(xiě)的疙渣,如果沒(méi)有做過(guò)底層相關(guān)的東西看起來(lái)還是非常吃力的匙奴,筆者對(duì)其中的一些細(xì)節(jié)也未完全搞懂,如果有興趣可以私下討論一些詳細(xì)的實(shí)現(xiàn)細(xì)節(jié)妄荔,其中有一些硬編碼的數(shù)字以及操作系統(tǒng)和硬件相關(guān)的規(guī)范理解起來(lái)相對(duì)比較困難泼菌。針對(duì) Golang runtime 中的幾大組件也會(huì)陸續(xù)寫(xiě)出相關(guān)的分析文章。

參考:

https://loulan.me/post/golang-boot/

https://mp.weixin.qq.com/s/W9D4Sl-6jYfcpczzdPfByQ

https://programmerall.com/article/6411655977/

https://ld246.com/article/1547651846124

https://zboya.github.io/post/go_scheduler/#mstartfn

https://blog.csdn.net/yockie/article/details/79166713

https://blog.csdn.net/ocean_1996/article/details/107088530

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末懦冰,一起剝皮案震驚了整個(gè)濱河市灶轰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌刷钢,老刑警劉巖笋颤,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡伴澄,警方通過(guò)查閱死者的電腦和手機(jī)赋除,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)非凌,“玉大人举农,你說(shuō)我怎么就攤上這事〕ㄎ耍” “怎么了颁糟?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng)团搞,這世上最難降的妖魔是什么坠狡? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任牍疏,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己障贸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布吟宦。 她就那樣靜靜地躺著篮洁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪殃姓。 梳的紋絲不亂的頭發(fā)上嘀粱,一...
    開(kāi)封第一講書(shū)人閱讀 49,730評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音辰狡,去河邊找鬼。 笑死垄分,一個(gè)胖子當(dāng)著我的面吹牛宛篇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播薄湿,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼叫倍,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了豺瘤?” 一聲冷哼從身側(cè)響起吆倦,我...
    開(kāi)封第一講書(shū)人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎坐求,沒(méi)想到半個(gè)月后蚕泽,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年须妻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了仔蝌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡荒吏,死狀恐怖敛惊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绰更,我是刑警寧澤瞧挤,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站儡湾,受9級(jí)特大地震影響特恬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜盒粮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一鸵鸥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧丹皱,春花似錦妒穴、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至呢簸,卻和暖如春矮台,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背根时。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工瘦赫, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蛤迎。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓确虱,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親替裆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子校辩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348