Mach-O探索

Mach-O探索

前言

我們都知道在iOS應(yīng)用程序中的可執(zhí)行文件的格式是Mach-O,那么Mach-O到底存儲(chǔ)了哪些數(shù)據(jù),又是怎么工作的呢?下面我們來探索一下。

1.Mach-O簡(jiǎn)介

維基百科對(duì)于Mach-O的描述:

Mach-OMach Object文件格式的縮寫,它是一種用于可執(zhí)行文件,目標(biāo)代碼,動(dòng)態(tài)庫,內(nèi)核轉(zhuǎn)儲(chǔ)的文件格式。作為a.out格式的替代,Mach-O提供了更強(qiáng)的擴(kuò)展性,并提升了符號(hào)表中信息的訪問速度。

Mach-O曾經(jīng)為大部分基于Mach核心的操作系統(tǒng)所使用。NeXTSTEPDarwinMac OS X等系統(tǒng)使用這種格式作為其原生可執(zhí)行文件,庫和目標(biāo)代碼的格式。而同樣使用GNU Mach作為其微內(nèi)核的GNU Hurd系統(tǒng)則使用ELF而非Mach-O作為其標(biāo)準(zhǔn)的二進(jìn)制文件格式。

Mach-O蘋果官方圖片.jpg
  • Header 包含了 Mach-O 文件的基本信息,如 CPU 架構(gòu),文件類型,加載指令數(shù)量等
  • Load Commands 是跟在 Header 后面的加載命令區(qū),包含文件的組織架構(gòu)和在虛擬內(nèi)存中的布局方式,在調(diào)用的時(shí)候知道如何設(shè)置和加載二進(jìn)制數(shù)據(jù)
  • Data 包含 Load Commands 中需要的各個(gè) Segment 的數(shù)據(jù)。

絕大多數(shù) Mach-O 文件包括以下三種 Segment:

  • __TEXT: 代碼段,包括頭文件、代碼和常量。只讀不可修改。
  • __DATA:數(shù)據(jù)段,包括全局變量, 靜態(tài)變量等。可讀可寫。
  • __LINKEDIT: 如何加載程序, 包含了方法和變量的元數(shù)據(jù)(位置,偏移量),以及代碼簽名等信息。只讀不可修改。

以下內(nèi)容參考自:WWDC 2016 Optimizing App Startup Time

1.1 Mach-O 的幾種類型

Mach-O的幾種類型.jpg
  • Executable類型

Executable 主程序的二進(jìn)制文件,就是我們iOS應(yīng)用程序顯示包內(nèi)容的MachO文件
可以通過Products->xxx.app->Show in Finder->顯示包內(nèi)容 查看

  • Dylib 類型

Dylib 動(dòng)態(tài)庫,在其他平臺(tái)上也叫DSO或者DLL

  • Bundle 類型

Bundle 不能被連接的Dylib,只能在Runtime運(yùn)行時(shí)通過dlopen函數(shù)來加載它,它可以在macOS上用于插件。

  • Image 類型

Image 是一種可執(zhí)行的二進(jìn)制文件或者包,包含了上述三種文件類型

  • Framework 類型

Framework其實(shí)也是一種dylib, 它周圍有一個(gè)特殊的目錄結(jié)構(gòu)來保存該dylib所需的文件。

那么這些都有什么區(qū)別和應(yīng)用呢,請(qǐng)參考我的另一篇文章iOS開發(fā)中『庫』的區(qū)別應(yīng)用

1.2 Mach-O結(jié)構(gòu)分析

1.2.1 segment 段

segment.jpg

Mach-O 文件是由 segment 段組成的,分別是TEXT段、DATA段、LINKEDIT段

  • 段的名稱為大寫格式
  • 所有段都是page size的倍數(shù)
  • arm64上段的大小為16K
  • 其他架構(gòu)均為4K

此處實(shí)際上是指的虛擬內(nèi)存的一頁

1.2.2 section

section.jpg

segment段內(nèi)部還有許多section節(jié),section的名稱為小寫。

But sections are really just a subrange of a segment, they don't have any of the constraints of being page size, but they are non-overlapping.
但是sections實(shí)際上只是segment段的子范圍,它們沒有頁面大小的限制,也不會(huì)重疊在一起。

通過MachOView也可以看出上述的結(jié)構(gòu):

MachOView.jpg

1.2.3 常見的 segment 與作用

  • __TEXT 代碼段,包括頭文件、代碼和常量以及mach header。 read-only(只讀的)
__TEXT.jpg
  • __DATA數(shù)據(jù)段,包括全局變量、靜態(tài)變量,是可讀可寫的。
__DATA.jpg
  • __LINKEDIT 如何加載程序,包括了方法和變量的元數(shù)據(jù)(位置、偏移量),以及代碼簽名等信息。只讀不可修改。
__LINKEDIT.jpg

1.2.4 Mach-O Universal Files

Universal Files.jpg

因?yàn)橛袝r(shí)候我們需要構(gòu)建多種架構(gòu)的Mach-O文件,這個(gè)時(shí)候的做法是通過Mach-O Universal Files來實(shí)現(xiàn)的,Xcode會(huì)重新生成不同架構(gòu)的二進(jìn)制文件,然后合并到一起,簡(jiǎn)稱Fat(胖)二進(jìn)制文件。它通過header來記錄不同架構(gòu)在文件中的偏移量,segment占多個(gè)分頁,header占用一頁空間,那么header占用一頁是不是浪費(fèi)了很多空間?答案是肯定的,那么為什么還要占用一頁空間呢?所有東西都基于頁面的好處是什么呢?下面我們通過虛擬內(nèi)存來解釋它。

1.3 virtual memory 虛擬內(nèi)存

virtual memory.jpg

PS: 軟件工程格言
every problem can be solved by adding a level of indirection.
每個(gè)問題都可以通過添加中間層來解決

所以說虛擬內(nèi)存是通過中間層間接尋址的一種技術(shù)

虛擬內(nèi)存解決是管理所有進(jìn)程使用物理內(nèi)存的問題。通過添加間接層來讓每個(gè)進(jìn)程使用邏輯地址空間,它可以映射到RAM上的某個(gè)物理頁面上,這種映射不是一對(duì)一的,邏輯地址也有可能映射不到RAM上,也有可能有多個(gè)邏輯地址映射到同一個(gè)物理RAM上。

virtual memory 應(yīng)用:

  1. 一個(gè)邏輯地址不映射任何物理RAM時(shí),進(jìn)程要訪問該地址時(shí)時(shí)會(huì)觸發(fā)page fault頁面錯(cuò)誤,內(nèi)核將停止該線程,并試圖找出解決方案,或者通過CPU調(diào)度去物理磁盤讀取缺失的內(nèi)容,或者其他處理
  2. 多個(gè)邏輯地址映射到同一物理RAM時(shí),兩個(gè)進(jìn)程共享一樣比特的RAM,通常就是我們說的共享緩存技術(shù),比如說我們的多個(gè)APP同時(shí)訪問UIKit
  3. 另一個(gè)就是文件的映射,不用把整個(gè)文件讀入RAM,而是可以調(diào)用mmap()函數(shù)告訴虛擬內(nèi)存系統(tǒng),我想把這部分文件映射到進(jìn)程里的這段地址,為什么要這樣做呢?不用讀取整個(gè)文件,通過設(shè)置該映射第一次訪問這些不同的地址時(shí),如果已經(jīng)在內(nèi)存里讀過,每次訪問未訪問過的地址時(shí),都會(huì)觸發(fā)page fault,內(nèi)核會(huì)處理該page fault,時(shí)間文件的懶加載
  4. 通過以上的介紹我們可以知道任意一個(gè)dylib或者image的TEXT段都可以映射到多個(gè)進(jìn)程中,并且可以實(shí)現(xiàn)懶加載,也可以實(shí)現(xiàn)進(jìn)程間共享。
  5. 那么DATA段呢?有一個(gè)策略叫寫入時(shí)復(fù)制,這個(gè)和APP的文件系統(tǒng)的克隆很相似,寫入時(shí)復(fù)制所做的是它積極地在所有進(jìn)程里共享DATA頁面,只要進(jìn)程只讀有共享內(nèi)容的全局變量,但是一旦有進(jìn)程想要寫入其DATA頁面,寫入時(shí)復(fù)制就是內(nèi)核會(huì)把該頁面進(jìn)行復(fù)制,放入另一個(gè)物理RAM并重定向映射,所以該進(jìn)程有了該頁面的副本,這把我們帶入了干凈頁面,該副本被認(rèn)為是臟頁面。臟頁面是指含有進(jìn)程特定信息,干凈頁面是指內(nèi)核可以按照需要重新建立頁面,比如重新讀取磁盤,所以臟頁面比干凈頁面要昂貴許多。
  6. 頁面的權(quán)限界限,這指的是可以標(biāo)記一個(gè)頁面可讀可寫可執(zhí)行,或者它們的任何組合。

1.4 virtual memory & Mach-O 之間的映射

首先我們擁有一個(gè)Dylib文件,我們還沒有把他讀取到物理內(nèi)存中,只是先進(jìn)行了映射。這時(shí)候靜態(tài)鏈接器會(huì)把所有值為0的全局變量都移動(dòng)到了尾端。

15984323319905.jpg

當(dāng)我們第一次訪問的時(shí)候,虛擬內(nèi)存會(huì)觸發(fā)page fault,這個(gè)時(shí)候內(nèi)核意識(shí)到它被映射到了一個(gè)文件,這個(gè)時(shí)候內(nèi)核會(huì)讀取這個(gè)文件將它放入物理RAM設(shè)置其映射。

first read.jpg

當(dāng)我們還需要讀取其他頁面的時(shí)候,比如讀取LINKEDITDATA的時(shí)候也是同樣的流程。

read other.jpg

但是當(dāng)我們要在DATA段寫入一些內(nèi)容的時(shí)候,就會(huì)觸發(fā)寫入時(shí)復(fù)制,這個(gè)時(shí)候DATA這個(gè)頁面就變?yōu)榕K頁面了,這個(gè)時(shí)候我們只有一個(gè)臟頁面和兩個(gè)干凈的頁面,如果一開始就加載全部,可能就都是臟頁面了。

臟頁面.jpg

此時(shí)如果另一個(gè)進(jìn)程也要加載該Dylib,就可以復(fù)用RAM1和RAM2,內(nèi)核只是簡(jiǎn)單的把映射重定向,不需要任何IO操作,如果DATA頁面那個(gè)RAM3沒有變成臟頁面也可以直接復(fù)用,如果變成臟頁面內(nèi)核會(huì)查看RAM3的副本是否在內(nèi)存中,如果還在就可以治截止使用,如果不在就會(huì)重新讀取。

15984334097424.jpg

這就實(shí)現(xiàn)了不同進(jìn)程共享這些Dylib,當(dāng)進(jìn)程都不需要使用某一段時(shí)比如LINKEDIT,在別的進(jìn)程需要RAM時(shí),就會(huì)將其釋放。

1.5 安全如何影響DYLD

Security.jpg

1.5.1 ASLR

ASLR (Address Space Layout Randomization) 地址空間布局隨機(jī)化,鏡像會(huì)在隨機(jī)的地址上加載。內(nèi)存偏移量還需要計(jì)算ASLR的位置。

1.5.1 Code Signing

在Xcode中 Code Signing是指對(duì)整個(gè)文件運(yùn)行一個(gè)加密哈希算法,然后對(duì)文件進(jìn)行一個(gè)簽名。為了在運(yùn)行時(shí)進(jìn)行驗(yàn)證,整個(gè)文件都必須要重新讀取,所以在編譯階段,在每個(gè)Mach-O文件的每一個(gè)頁面都進(jìn)行自己的加密哈希算法,所有哈希都存儲(chǔ)在LINKEDIT里,這使得你的每個(gè)未被修改的頁面,在被讀取的過程中都能得到及時(shí)驗(yàn)證。

1.6 exec()

exec.jpg

Exec is a system call. When you trap into the kernel, you basically say I want to replace this process with this new program.

exec 是一個(gè)系統(tǒng)調(diào)用,當(dāng)你進(jìn)入內(nèi)核,我想把這個(gè)進(jìn)程換成這個(gè)新程序,內(nèi)核會(huì)抹去整個(gè)地址,映射指定的可執(zhí)行程序,ASLR把它映射到一個(gè)隨機(jī)地址,下一步是從該隨機(jī)地址回溯到0地址把整個(gè)區(qū)域標(biāo)記為不可訪問,就是不可讀,不可寫,不可執(zhí)行,該區(qū)域在32位處理器下至少4KB大小,64位處理器下至少4GB大小,這樣就可以捕獲任何空指針引用,捕獲任何指針截?cái)唷?/p>

2. dyld

2.1 dyld 簡(jiǎn)介

dyld.jpg

Unix 誕生初期一切都很簡(jiǎn)單,我只需映射一個(gè)程序,把指針引用指向它,開始運(yùn)行即可,后來人們有發(fā)明了共享緩存庫,那么誰來加載Dylibs呢?這是件很復(fù)雜的事情,人們意識(shí)到不能讓內(nèi)核來做這件事,所以幫助程序就誕生了在Unix平臺(tái)人們叫它LD.SO,在iOS上他被叫做DYLD

當(dāng)內(nèi)核完成進(jìn)程的映射,它現(xiàn)在映射到另一個(gè)Mach-O文件,調(diào)用Dyld進(jìn)入該進(jìn)程到另一個(gè)隨機(jī)地址,把指針引用指向Dyld,讓Dyld完成進(jìn)程的啟動(dòng),Dyld的工作是加載所有依賴的Dylib,讓它們?nèi)繙?zhǔn)備好,開始運(yùn)行。

2.2 dyld加載Mach-O流程

2.2.1 加載主流程(時(shí)間軸)

dyld加載流程.jpg

2.2.2 Load dylibs

Load dylibs done.jpg
  1. Dyld首先要根據(jù)內(nèi)核映射好的主可執(zhí)行文件的頭文件,該頭文件里有一個(gè)所有依賴的庫的列表,根據(jù)這個(gè)列表映射所有Dylib
  2. 找到所有Dylib后,確定它是一個(gè)MachO文件后,通過代碼簽名對(duì)他進(jìn)行驗(yàn)證并注冊(cè)到內(nèi)核。
  3. 然后它可以在該Dylib里的每一段調(diào)用mmap(),將其讀入內(nèi)存。
  4. Dyld還會(huì)對(duì)每個(gè)Dylib進(jìn)行遞歸加載,因?yàn)槊總€(gè)不同的Dylib還有可能依賴Dylib(已加載的或者未加載的),直到全部加載完畢。
  5. 其實(shí)我們需要加載的Dylib有很多,大約有100400個(gè),但是大部分都是OS Dylib,這里系統(tǒng)為我們做了足夠多的優(yōu)化,以確保加載速度非常非常的快。

2.2.3 Fix-Ups

現(xiàn)在Dylibs都已經(jīng)加載完畢了,但是它們都是彼此獨(dú)立的,我們下一條把它們綁定在一起。這就是Fix-Ups(修復(fù))。

code sign.jpg

由于代碼簽名的存在,我們無法修改指令,那么dylib該如何調(diào)用另一dylib呢?這個(gè)時(shí)候我們使用code-gen,即動(dòng)態(tài)PIC(Position Independent Code) 地址無關(guān)代碼,代碼可以加載到該地址,并且是動(dòng)態(tài)的,也就是說地址間接的被分配,為了一個(gè)調(diào)用另一個(gè),code-gen實(shí)際上在DATA段新建了一個(gè)指向被調(diào)用者的指針,任何加載該指針并跳轉(zhuǎn)過去。

Rebasing and Binding.jpg

所以所有的dyld都在修復(fù)指針和數(shù)據(jù),修復(fù)有兩種,一種是重設(shè)基址,另一種是綁定重設(shè)基址是指如果有一個(gè)指針指向Image內(nèi),需要作出所有修改;綁定是指 如果指針指向Image范圍外,也需要進(jìn)行不同的修復(fù)。

dyldinfo還有很多選項(xiàng)參數(shù),我們可以在任何二進(jìn)制文件上運(yùn)行,就可以看到所有的修復(fù)。

fixup.jpg

過去你可以為每一個(gè)dylib指定首選加載地址,該首選加載地址是一個(gè)靜態(tài)指針和dyld一起合作,比如若把它加載到該首選加載地址,所有指針和數(shù)據(jù)本應(yīng)該是內(nèi)部編碼的,都是正確的,那么dyld就不用做任何修復(fù)。現(xiàn)在有了ASLR ,dylib被加載到隨機(jī)地址上,它偏移到了其他的地址,也就是說所有的指針和數(shù)據(jù)都依然指向舊地址,所以為了修復(fù)它們,我們需要計(jì)算偏移值,并且對(duì)每一個(gè)內(nèi)部指針都添加該偏移值,所以重設(shè)基址就是指遍歷所有內(nèi)部數(shù)據(jù)指針,然后為它們添加一個(gè)偏移值。概念非常簡(jiǎn)單,就是讀取一個(gè)指針,添加偏移值,在寫入新值。那么這些數(shù)據(jù)指針都在哪里呢?這些指針都在LINKEDIT段里存儲(chǔ)著。此時(shí)所有的映射都已經(jīng)結(jié)束,當(dāng)我們開始重設(shè)基址的時(shí)候?qū)嶋H上所有DATA頁面上都產(chǎn)生了錯(cuò)誤,然后對(duì)頁面進(jìn)行修改,觸發(fā)寫入時(shí)復(fù)制,所有的重設(shè)基址有時(shí)會(huì)非常昂貴,由于這些都需要I/O操作,但是有一個(gè)技巧,就是按順序操作,從內(nèi)核的角度來看,它認(rèn)為數(shù)據(jù)錯(cuò)誤順序按照產(chǎn)生,當(dāng)它如此認(rèn)為時(shí),內(nèi)核會(huì)進(jìn)行預(yù)讀,這樣就會(huì)降低很多I/O成本

Rebasing.jpg

2.2.4 Binding

Binding.jpg

綁定是針對(duì)那些指向dylib范圍外的指針而言的,這些指針通過名稱就是綁定,實(shí)際就是字符串,本例中LINKEDIT段里的malloc,也就是說該數(shù)據(jù)指針需要指向malloc,所以在運(yùn)行時(shí)dyld需要找到實(shí)現(xiàn)該符號(hào)的位置,這需要很多的計(jì)算,遍歷查找符號(hào)表,一旦找到就把值存到該數(shù)據(jù)指針里,計(jì)算復(fù)雜度比重設(shè)基址高的多。

2.2.5 Notify ObjC Runtime

Notify ObjC Runtime.jpg
  1. Objc有很多DATA結(jié)構(gòu),DATA結(jié)構(gòu)類,也就是指向方法的指針,以及高光指針,幾乎都已經(jīng)被修復(fù),通過重設(shè)基址或者綁定。
  2. 但是在Objc運(yùn)行時(shí)還需要一些額外的操作,首先Objc是一門動(dòng)態(tài)語言,可以把一個(gè)類用名稱實(shí)例化,即Objc運(yùn)行時(shí)需要維護(hù)一張表,包含所有名稱及其映射的類,每次加載的名稱都將定義一個(gè)類,名稱需要登記在一個(gè)全局表格里。
  3. 在C++中你可能聽說過脆弱的基類問題,但是在Objc中就不存在該問題,因?yàn)槲覀冏龅钠渲幸环N修復(fù)就是,在加載時(shí)動(dòng)態(tài)改變所有ivar的偏移量。
  4. 在Objc里可以定義Categories,有時(shí)候它們?cè)诹硪粋€(gè)dylib里,此時(shí)那些方法修復(fù)必須已經(jīng)完成。
  5. Objc基于選擇器是唯一的,所以我們需要唯一的選擇器

2.2.6 Initializers

So 我們現(xiàn)在完成了所有所有靜態(tài)描述的DATA的修復(fù),現(xiàn)在是進(jìn)行動(dòng)態(tài)DATA修復(fù)的時(shí)機(jī)。

Initializers.jpg
  1. 在C++中有一個(gè)叫做Initializers的初始化器,可以指定你想要的任何表達(dá)式,在這里我們可以通過運(yùn)行初始化器來完成那些抽象表達(dá)式的初始化。
  2. 在Objc有一種方法叫+load方法,但是現(xiàn)在+load方法已經(jīng)不在建議使用(建議使用+initialize),如果使用了它現(xiàn)在將開始運(yùn)行
  3. 頂端是主可執(zhí)行文件,所有的dylibs依照這張大圖,必須要運(yùn)行初始化器從下往上運(yùn)行,原因是當(dāng)初始化器運(yùn)行時(shí)可能會(huì)調(diào)用一些dylib,你需要確保那些dylib已經(jīng)準(zhǔn)備好被調(diào)用。從下往上一直到類,可以很安全的調(diào)用依賴的內(nèi)容
  4. 但所有初始化器完成時(shí),我們實(shí)際已經(jīng)最終調(diào)用的主Dylib程序

dyld是一個(gè)幫助程序:

  1. 可以加載所有的依賴庫
  2. 修復(fù)所有DATA頁面
  3. 運(yùn)行初始化器,跳轉(zhuǎn)到主函數(shù)

2.3 dyld2 && dyld3

詳見WWDC2017 - 413 - App Startup Time: Past, Present, and Future

dyld2 && dyld3.jpg

iOS 13之前,所有APP都是通過dyld2來啟動(dòng)的,主要過程如下:

  1. 解析MachOHeaderLoad Commands,找到其依賴的庫,并遞歸找到所有依賴的庫
  2. 加載MachO文件
  3. 進(jìn)行符號(hào)查找
  4. 綁定和重設(shè)基址
  5. 運(yùn)行初始化程序

dyld3被分為了三個(gè)組件:

  • 一個(gè)進(jìn)程外的MachO解析器
    • 預(yù)先處理了所有可能影響啟動(dòng)速度的Search Path@rpaths和環(huán)境變量
    • 開始分析MachOHeader和依賴,并完成了所有符號(hào)查找的工作
    • 最后將這些結(jié)果創(chuàng)建成了一個(gè)啟動(dòng)包
    • 這是一個(gè)普通的 daemon 進(jìn)程,可以使用通常的測(cè)試架構(gòu)
  • 一個(gè)進(jìn)程內(nèi)的引擎,用來運(yùn)行啟動(dòng)閉包
    • 這部分在進(jìn)程中處理
    • 驗(yàn)證啟動(dòng)閉包的安全性,然后映射到dylib中,在跳轉(zhuǎn)到main函數(shù)
    • 不需要解析Mach-OHeader和依賴,也不需要符號(hào)查找
  • 一個(gè)啟動(dòng)閉包緩存服務(wù)
    • 系統(tǒng)APP的啟動(dòng)閉包被構(gòu)建在一個(gè)Shared Cache 中,我們甚至不需要打開一個(gè)單獨(dú)的文件
    • 對(duì)于第三方的APP,我們會(huì)在APP安裝或者升級(jí)的時(shí)候構(gòu)建這個(gè)啟動(dòng)閉包
    • 在iOS、tvOS、watchOS中,這一切都是APP啟動(dòng)之前完成的,在macOS上,由于有Side Load App,進(jìn)程內(nèi)引擎會(huì)在首次啟動(dòng)的時(shí)候啟動(dòng)一個(gè)daemon進(jìn)程,之后就可以啟動(dòng)閉包了。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,412評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,514評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,373評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,975評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,743評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,199評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,262評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,414評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,951評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,780評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,983評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,527評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,218評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,649評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,889評(píng)論 1 286
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,673評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,967評(píng)論 2 374