Mach-O 可執(zhí)行文件

這篇文章主要了介紹以下兩點(diǎn):

  • 從源代碼到可執(zhí)行文件傲醉,編譯器都做了什么讶踪?
  • Mach-O 可執(zhí)行文件里面是什么?

注:這篇文章的討論和示例不使用 Xcode缠犀,只使用命令行。

準(zhǔn)備工作:Xcode 工具鏈

xcrun 是 Xcode 基本的命令行工具聪舒,使用 xcrun 可以調(diào)用其他工具夭坪。

比如查看 clang 的版本,我們可以執(zhí)行下面的命令:

$ xcrun clang -v

而不是:

$ clang -v

如果要使用某個工具过椎,直接執(zhí)行那個工具的命令就行了室梅,為什么要使用 xcrun 呢?
因?yàn)槿绻愕碾娔X上安裝有多個不同版本的 Xcode,借助 xcrunxcode-select 你可以:

  • 選擇指定 Xcode 版本下的工具
  • 選擇指定 Xcode 版本下的 SDK

如果你的電腦上只安裝了一個 Xcode亡鼠,就沒必要使用 xcrun 了赏殃。

一、不使用 IDE 來實(shí)現(xiàn)一個 Hello World

使用 clang 編譯一個簡單的 Hello World 小程序间涵,然后就可以直接執(zhí)行最后生成的 a.out 文件了仁热。

編寫 helloworld.c:

#include <stdio.h>

int main(int argc, char *argv[])
{
    printf("Hello World!\n");
    return 0;
}

然后使用 clang 將該文件編譯成一個 Mach-O 二進(jìn)制文件 a.out,并執(zhí)行這個 a.out 文件:

$ xcrun clang helloworld.c
$ ./a.out

最終可以看到終端上輸出了 Hello World!勾哩。

這個 a.out 是怎么生成的呢抗蠢?

二、編譯器是如何工作的

在上面的例子中思劳,我們所選用的編譯器是 clang迅矛,編譯器在將 helloworld.c 編譯成一個可執(zhí)行文件時,需要經(jīng)過好幾步潜叛。

編譯器處理的幾個步驟:

  • Preprocessing
    • Tokenization
    • Macro expansion
    • #include expansion
  • Parsing and Semantic Analysis
    • Translates preprocessor tokens into a parse tree
    • Applies semantic analysis to the parse tree
    • Outputs an Abstract Syntax Tree (AST)
  • Code Generation and Optimization
    • Translates an AST into low-level intermediate code (LLVM IR)
    • Responsible for optimizing the generated code
    • target-specific code generation
    • Outputs assembly
  • Assembler
    • Translates assembly code into a target object file
  • Linker
    • Merges multiple object files into an executable (or a dynamic library)

1. 預(yù)處理

這個過程主要是對源代碼進(jìn)行標(biāo)記拆分秽褒、宏展開、#include 展開等等威兜。

使用下面的命令可以看到 helloworld.c 預(yù)處理后的結(jié)果:

$ xcrun clang -E helloworld.c

我們也可以將輸出的結(jié)果在文本編輯器中打開:

$ xcrun clang -E helloworld.c | open -f

最后得到的預(yù)處理結(jié)果大概有 542 行:

...

# 52 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/include/secure/_stdio.h" 3 4
extern int __snprintf_chk (char * restrict, size_t, int, size_t,
      const char * restrict, ...);

extern int __vsprintf_chk (char * restrict, int, size_t,
      const char * restrict, va_list);

extern int __vsnprintf_chk (char * restrict, size_t, int, size_t,
       const char * restrict, va_list);
# 412 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/include/stdio.h" 2 3 4
# 2 "helloworld.c" 2
int main(int argc, char *argv[])
{
    printf("Hello World!\n");
    return 0;
}

與處理結(jié)果中那些 # 開頭的語句表示行標(biāo)記(linemarker)销斟,告訴我們后面接下來的內(nèi)容來自哪個文件的哪一行。

helloworld.c 中的 #include <stdio.h> 告訴預(yù)處理器要在那個地方插入 stdio.h 的內(nèi)容椒舵。這是一個遞歸的過程蚂踊,如果 stdio.h 中也引入了其他的 .h 文件,在預(yù)處理時同樣也會把這些語句替換成源文件中的內(nèi)容笔宿。

Tips: 在 Xcode 中打開菜單 Product -> Perform Action -> Preprocess悴势,可以查看當(dāng)前打開文件的預(yù)處理結(jié)果。

2. 編譯

這個過程主要是對預(yù)處理后的代碼進(jìn)行語法分析措伐、語義分析特纤,并生成語法樹(AST),然后再翻譯成中間代碼侥加,并優(yōu)化代碼捧存,最后再針對不同平臺生成對應(yīng)的代碼,并轉(zhuǎn)成匯編代碼担败。

我們可以使用下面的命令生成匯編代碼:

$ xcrun clang -S -o - helloworld.c | open -f

生成的匯編代碼如下:

  .section  __TEXT,__text,regular,pure_instructions
  .macosx_version_min 10, 13
  .globl  _main                   ## -- Begin function main
  .p2align  4, 0x90
_main:                                  ## @main
  .cfi_startproc
## %bb.0:
  pushq %rbp
  .cfi_def_cfa_offset 16
  .cfi_offset %rbp, -16
  movq  %rsp, %rbp
  .cfi_def_cfa_register %rbp
  subq  $32, %rsp
  leaq  L_.str(%rip), %rax
  movl  $0, -4(%rbp)
  movl  %edi, -8(%rbp)
  movq  %rsi, -16(%rbp)
  movq  %rax, %rdi
  movb  $0, %al
  callq _printf
  xorl  %ecx, %ecx
  movl  %eax, -20(%rbp)         ## 4-byte Spill
  movl  %ecx, %eax
  addq  $32, %rsp
  popq  %rbp
  retq
  .cfi_endproc
                                        ## -- End function
  .section  __TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
  .asciz  "Hello World!\n"


.subsections_via_symbols

. 開頭的是匯編器的指令昔穴。

.section 指令表示的是接下來的 section 是什么內(nèi)容。

.globl 指令表示 _main 是一個外部符號提前,也就是要暴露給其他模塊使用的符號吗货。

.p2align 指令表示的是字節(jié)對齊的規(guī)則是什么。

.cfi_startproc 表示一個函數(shù)的開始狈网,相應(yīng)地宙搬,.cfi_endproc表示一個函數(shù)的結(jié)束笨腥。cfi 是 Call Frame Information 的縮寫。

.cfi_def_cfa_offset 16.cfi_offset %rbp, -16 也是 cfi 指令勇垛,用來輸出一些函數(shù)堆棧展開信息和調(diào)試信息的脖母。

L_.str 標(biāo)簽可以讓我們在代碼中通過指針訪問到一個字符串常量。

.asciz 命令告訴匯編器輸出一個字面量字符串闲孤。

最后的 .subsections_via_symbols 是留給靜態(tài)鏈接編輯器使用的谆级。

Tips: 類似地,在 Xcode 中打開菜單 Product -> Perform Action -> Assemble讼积,可以查看當(dāng)前打開文件的匯編代碼肥照。

3. 匯編

匯編的過程就是將匯編代碼翻譯成機(jī)器代碼,生成目標(biāo)文件勤众。

當(dāng)你用 Xcode 構(gòu)建你的 iOS App 時舆绎,你可以在你的項(xiàng)目的 Derived Data 目錄下找到一個 Objects-normal 文件夾,里面就是 .m 文件編譯后生成的目標(biāo)文件决摧。

4. 鏈接

鏈接器負(fù)責(zé)將各個目標(biāo)文件和庫合并成一個完整的可執(zhí)行文件亿蒸。在這個過程中凑兰,鏈接器需要解析各個目標(biāo)文件和庫之間的符號引用掌桩。

helloworld.c 中調(diào)用了 printf() 函數(shù),這個函數(shù)定義在 libc 庫中姑食,但是最終的可執(zhí)行文件需要知道 printf() 在內(nèi)存中的什么地方波岛,也就是 _printf 符號的地址。

鏈接器在鏈接時就會把所有的目標(biāo)文件(在我們這個例子中就是 helloworld.o)和庫(libc)作為輸入文件音半,然后解析它們之間符號引用(_printf 符號)则拷,最終生成一個可以運(yùn)行的可執(zhí)行文件。

二曹鸠、可執(zhí)行文件

一個可執(zhí)行文件中包含多個不同的 segment煌茬,,一個 segment 又包含一個或多個 section彻桃。

我們可以使用 size 工具查看目標(biāo)文件中的各個 section:

xcrun size -x -l -m a.out 

下面是 helloworld.c 的目標(biāo)文件的各個 segment 和 section 的內(nèi)容:

Segment __PAGEZERO: 0x100000000 (vmaddr 0x0 fileoff 0)
Segment __TEXT: 0x1000 (vmaddr 0x100000000 fileoff 0)
  Section __text: 0x34 (addr 0x100000f50 offset 3920)
  Section __stubs: 0x6 (addr 0x100000f84 offset 3972)
  Section __stub_helper: 0x1a (addr 0x100000f8c offset 3980)
  Section __cstring: 0xe (addr 0x100000fa6 offset 4006)
  Section __unwind_info: 0x48 (addr 0x100000fb4 offset 4020)
  total 0xaa
Segment __DATA: 0x1000 (vmaddr 0x100001000 fileoff 4096)
  Section __nl_symbol_ptr: 0x10 (addr 0x100001000 offset 4096)
  Section __la_symbol_ptr: 0x8 (addr 0x100001010 offset 4112)
  total 0x18
Segment __LINKEDIT: 0x1000 (vmaddr 0x100002000 fileoff 8192)
total 0x100003000

當(dāng)我們運(yùn)行可執(zhí)行文件時坛善,系統(tǒng)會把各個 segment 映射到進(jìn)程的地址空間中,在映射時邻眷,各個 segment 和 section 被分配不同的屬性眠屎,也就是權(quán)限。

我們來看看各個 segment 和 section 的具體含義:

  • __PAGEZERO:從上面的信息中可以看出肆饶,這塊區(qū)域占 4 個 G 的大小改衩,不可讀不可寫,不可執(zhí)行驯镊,
  • __TEXT:代碼區(qū)葫督,具有只讀竭鞍、可執(zhí)行的權(quán)限
    • __text:編譯后生成的機(jī)器碼
    • __stubs:用于動態(tài)鏈接
    • __stub_helper:用于動態(tài)鏈接
    • __cstring:字面量字符串,也就是寫在代碼里的字符串
    • __unwind_info
    • __const:常量
  • __DATA:數(shù)據(jù)區(qū)候衍,可讀可寫笼蛛,但是不可執(zhí)行
    • __nl_symbol_ptr:non-lazy symbol pointers,局部符號蛉鹿,也就是定義在該文件內(nèi)的符號
    • __la_symbol_ptr:lazy symbol pointers滨砍,外部符號,也就是定義在該文件外的符號
    • __const:需要重定位的常量
    • __bss:未初始化的靜態(tài)變量
    • __common:未初始化的外部全局變量
    • __dyld:給動態(tài)鏈接器使用的
  • __LINKEDIT

1. Section Content

我們可以使用 otool 查看目標(biāo)文件中指定 section 的內(nèi)容:

xcrun otool -s __TEXT __text a.out 

得到的結(jié)果如下:

a.out:
Contents of (__TEXT,__text) section
0000000100000f50  55 48 89 e5 48 83 ec 20 48 8d 05 47 00 00 00 c7
0000000100000f60  45 fc 00 00 00 00 89 7d f8 48 89 75 f0 48 89 c7
0000000100000f70  b0 00 e8 0d 00 00 00 31 c9 89 45 ec 89 c8 48 83
0000000100000f80  c4 20 5d c3

上面的機(jī)器代碼幾乎沒辦法看懂妖异,不過我們可以使用 otool 來查看反匯編后的代碼:

xcrun otool -v -t a.out

得到的結(jié)果如下:

a.out:
(__TEXT,__text) section
_main:
0000000100000f50  pushq %rbp
0000000100000f51  movq  %rsp, %rbp
0000000100000f54  subq  $0x20, %rsp
0000000100000f58  leaq  0x47(%rip), %rax
0000000100000f5f  movl  $0x0, -0x4(%rbp)
0000000100000f66  movl  %edi, -0x8(%rbp)
0000000100000f69  movq  %rsi, -0x10(%rbp)
0000000100000f6d  movq  %rax, %rdi
0000000100000f70  movb  $0x0, %al
0000000100000f72  callq 0x100000f84
0000000100000f77  xorl  %ecx, %ecx
0000000100000f79  movl  %eax, -0x14(%rbp)
0000000100000f7c  movl  %ecx, %eax
0000000100000f7e  addq  $0x20, %rsp
0000000100000f82  popq  %rbp
0000000100000f83  retq

2. Mach-O

Mach-O 是 Mach object file 格式的縮寫惋戏,Mach-O 是一種可執(zhí)行文件,Mac OS 上的可執(zhí)行文件都是 Mach-O 格式的他膳。

使用下面的命令可以查看一下 a.out 的文件格式:

$ file a.out 
a.out: Mach-O 64-bit executable x86_64

我們可以使用 otool 查看可執(zhí)行文件的 Mach-O header:

$ otool -v -h a.out

得到的結(jié)果如下:

Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64  X86_64        ALL LIB64     EXECUTE    15       1200   NOUNDEFS DYLDLINK TWOLEVEL PIE

ncmds 和 sizeofcmds 表示的是加載命令(load commands)响逢,可以通過 -l 參數(shù)查看詳細(xì)信息:

otool -v -l a.out | open -f
a.out:
Load command 0
      cmd LC_SEGMENT_64
  cmdsize 72
  segname __PAGEZERO
   vmaddr 0x0000000000000000
   vmsize 0x0000000100000000
...

找到 Load command 1 部分的 initprot 字段,其值為 r-x棕孙,表示 read-only 和 executable舔亭。

load command 指定了每一個 segment 和每個 section 的內(nèi)存地址以及權(quán)限保護(hù)。

下面是 __TEXT __text section 的信息:

...
Section
  sectname __text
   segname __TEXT
      addr 0x0000000100000f50
      size 0x0000000000000034
    offset 3920
     align 2^4 (16)
    reloff 0
    nreloc 0
      type S_REGULAR
attributes PURE_INSTRUCTIONS SOME_INSTRUCTIONS
 reserved1 0
 reserved2 0

 ...

這段代碼的 addr 值是 0x0000000100000f50蟀俊,跟上面用 xcrun otool -v -t a.out 查看的 _main 的入口地址是一樣的钦铺。

三、一個更復(fù)雜的例子

我們現(xiàn)在有三個文件肢预,F(xiàn)oo.h矛洞、Foo.m 和 helloworld.m,如下烫映。

Foo.h:

#import <Foundation/Foundation.h>

@interface Foo : NSObject

- (void)run;

@end

Foo.m:

#import "Foo.h"

@implementation Foo

- (void)run
{
    NSLog(@"%@", NSFullUserName());
}

@end

helloworld.m:

#import "Foo.h"

int main(int argc, char *argv[])
{
    @autoreleasepool {
        Foo *foo = [[Foo alloc] init];
        [foo run];
        return 0;
    }
}

1. 編譯

分別編譯 Foo.m 和 helloworld.m 這兩個文件:

$ xcrun clang -c Foo.m
$ xcrun clang -c helloworld.m

問題:為什么我們不需要編譯 .h 文件沼本?
因?yàn)轭^文件存在的目的,就是為了讓我們能通過 importinclude 實(shí)現(xiàn)在多個不同的文件中共享一些代碼(比如函數(shù)聲明锭沟、變量聲明和類聲明等)抽兆,這樣我們就不用在每個用到相同聲明的地方寫重復(fù)代碼了。

得到兩個目標(biāo)文件:

$ file Foo.o helloworld.o
Foo.o:        Mach-O 64-bit object x86_64
helloworld.o: Mach-O 64-bit object x86_64

為了能夠得到一個可執(zhí)行文件族淮,我們需要將這兩個目標(biāo)文件以及 Foundation 框架鏈接起來:


xcrun clang helloworld.o Foo.o -Wl,`xcrun --show-sdk-path`/System/Library/Frameworks/Foundation.framework/Foundation

我們會得到一個最終的可執(zhí)行文件 a.out辫红,然后我們在執(zhí)行這個文件,可以看到打印的結(jié)果:

$ ./a.out
2019-02-02 17:27:18.207 a.out[4181:265495] ShannonChen

2. 符號解析和鏈接

Foo.ohelloworld.o 都用到了 Foundation 框架瞧筛,helloworld.o 中用到了 autorelease pool厉熟,而且 Foo.ohelloworld.o 都在 libobjc.dylib 的幫助下間接使用了 Objective-C runtime,因?yàn)?Objective-C 方法調(diào)用時發(fā)送消息需要用到 runtime较幌。

什么是符號揍瑟?

每一個我們定義的或者用到的函數(shù)、全局變量和類都是符號乍炉。

在鏈接時绢片,鏈接器會解析各個目標(biāo)文件以及庫之間的符號滤馍,每個目標(biāo)文件都有一個符號表來說明它的符號。

我們可以使用工具 nm 來查看目標(biāo)文件 helloworld.o 的所有符號:

$ xcrun nm -nm helloworld.o
                 (undefined) external _OBJC_CLASS_$_Foo
                 (undefined) external _objc_autoreleasePoolPop
                 (undefined) external _objc_autoreleasePoolPush
                 (undefined) external _objc_msgSend
0000000000000000 (__TEXT,__text) external _main

_OBJC_CLASS_$_Foo 符號就是我們定義的 Objective-C 類 Foo底循,我可以看到巢株,這個符號的解析狀態(tài)是 undefined(因?yàn)?helloworld.o 中引用了 Foo 類,但是沒有定義這個類)熙涤,屬性是 external(表示這個 Foo 類不是私有的)阁苞。

_main 符號對應(yīng)的就是我們的 main() 函數(shù),它的屬性也是 external祠挫,因?yàn)樗侨肟诤瘮?shù)那槽,需要暴露出來被系統(tǒng)調(diào)用(值得注意的是,它的地址是 0)等舔。

然后骚灸,我們再看看目標(biāo)文件 Foo.o 中的所有符號:

xcrun nm -nm Foo.o
                 (undefined) external _NSFullUserName
                 (undefined) external _NSLog
                 (undefined) external _OBJC_CLASS_$_NSObject
                 (undefined) external _OBJC_METACLASS_$_NSObject
                 (undefined) external ___CFConstantStringClassReference
                 (undefined) external __objc_empty_cache
0000000000000000 (__TEXT,__text) non-external -[Foo run]
0000000000000060 (__DATA,__objc_const) non-external l_OBJC_METACLASS_RO_$_Foo
00000000000000a8 (__DATA,__objc_const) non-external l_OBJC_$_INSTANCE_METHODS_Foo
00000000000000c8 (__DATA,__objc_const) non-external l_OBJC_CLASS_RO_$_Foo
0000000000000110 (__DATA,__objc_data) external _OBJC_METACLASS_$_Foo
0000000000000138 (__DATA,__objc_data) external _OBJC_CLASS_$_Foo

在這里,_OBJC_CLASS_$_Foo 符號不再是 undefined 的了慌植,因?yàn)?Foo.o 中定義了 Foo 這個類甚牲。

當(dāng)這兩個目標(biāo)文件和 Foundation 庫鏈接時,鏈接器就會根據(jù)上面的這些符號表解析目標(biāo)文件中的符號蝶柿,解析成功后就能知道這個符號的地址了丈钙。

最后,我們再看看最終生成的可執(zhí)行文件的符號表信息:

xcrun nm -nm a.out
                 (undefined) external _NSFullUserName (from Foundation)
                 (undefined) external _NSLog (from Foundation)
                 (undefined) external _OBJC_CLASS_$_NSObject (from libobjc)
                 (undefined) external _OBJC_METACLASS_$_NSObject (from libobjc)
                 (undefined) external ___CFConstantStringClassReference (from CoreFoundation)
                 (undefined) external __objc_empty_cache (from libobjc)
                 (undefined) external _objc_autoreleasePoolPop (from libobjc)
                 (undefined) external _objc_autoreleasePoolPush (from libobjc)
                 (undefined) external _objc_msgSend (from libobjc)
                 (undefined) external dyld_stub_binder (from libSystem)
0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header
0000000100000e90 (__TEXT,__text) external _main
0000000100000f10 (__TEXT,__text) non-external -[Foo run]
0000000100001138 (__DATA,__objc_data) external _OBJC_METACLASS_$_Foo
0000000100001160 (__DATA,__objc_data) external _OBJC_CLASS_$_Foo

我們可以看到只锭,跟 Foundation 和 Objective-C runtime 相關(guān)的符號依然是 undefined 狀態(tài)(這些需要在加載程序進(jìn)行動態(tài)鏈接時來解析)著恩,但是這個符號表中已經(jīng)有了如何解析這些符號的信息院尔,也就是從哪里可以找到這些符號蜻展。

比如,符號 _NSLog 后面有一個 from Foundation 的說明邀摆,這樣在動態(tài)鏈接時就知道是去 Foundation 庫找 _NSLog 這個符號的定義了纵顾。

而且,可執(zhí)行文件知道去哪里找到這些需要參與鏈接的動態(tài)庫:

$ xcrun otool -L a.out
a.out:
  /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 1555.10.0)
  /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.200.5)
  /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1555.10.0)
  /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)

這些 undefined symbols 會在運(yùn)行時被動態(tài)鏈接器 dyld 解析栋盹,當(dāng)我們運(yùn)行這個可執(zhí)行文件時施逾, dyld 可以保證 _NSFullUserName 這些符號能夠指向它們在 Foundation 以及其他動態(tài)庫中的實(shí)現(xiàn)。

3. dyld 的共享緩存

有些應(yīng)用程序可能會用到大量的 framework 和動態(tài)庫例获,這樣在鏈接時就會有成千上萬的符號需要解析汉额,從而影響鏈接速度。

為了縮短這個流程榨汤,在 macOS 和 iOS 上會針對每個架構(gòu)蠕搜,預(yù)先將所有的動態(tài)庫鏈接成一個庫,緩存到 /var/db/dyld/ 目錄下收壕。當(dāng)一個 Mach-O 文件被加載到內(nèi)存中時妓灌,動態(tài)鏈接器首先去緩存目錄中檢查是否有緩存轨蛤,如果有就直接使用緩存好的動態(tài)庫。通過這種方式虫埂,大大提高了 macOS 和 iOS 上的應(yīng)用啟動速度祥山。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市掉伏,隨后出現(xiàn)的幾起案子缝呕,更是在濱河造成了極大的恐慌,老刑警劉巖斧散,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岳颇,死亡現(xiàn)場離奇詭異,居然都是意外死亡颅湘,警方通過查閱死者的電腦和手機(jī)话侧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來闯参,“玉大人瞻鹏,你說我怎么就攤上這事÷拐” “怎么了新博?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長脚草。 經(jīng)常有香客問我赫悄,道長,這世上最難降的妖魔是什么馏慨? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任埂淮,我火速辦了婚禮,結(jié)果婚禮上写隶,老公的妹妹穿的比我還像新娘倔撞。我一直安慰自己,他們只是感情好慕趴,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布痪蝇。 她就那樣靜靜地躺著,像睡著了一般冕房。 火紅的嫁衣襯著肌膚如雪躏啰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天耙册,我揣著相機(jī)與錄音给僵,去河邊找鬼。 笑死觅玻,一個胖子當(dāng)著我的面吹牛想际,可吹牛的內(nèi)容都是我干的培漏。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼胡本,長吁一口氣:“原來是場噩夢啊……” “哼牌柄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起侧甫,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤珊佣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后披粟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體咒锻,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年守屉,在試婚紗的時候發(fā)現(xiàn)自己被綠了惑艇。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡拇泛,死狀恐怖滨巴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情俺叭,我是刑警寧澤恭取,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站熄守,受9級特大地震影響蜈垮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜裕照,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一攒发、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧牍氛,春花似錦晨继、人聲如沸烟阐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蜒茄。三九已至唉擂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間檀葛,已是汗流浹背玩祟。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留屿聋,地道東北人空扎。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓藏鹊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親转锈。 傳聞我的和親對象是個殘疾皇子盘寡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

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