iOS應(yīng)用 main 執(zhí)行前發(fā)生的事情

這篇是對(duì) iOS 應(yīng)用啟動(dòng)時(shí),main 函數(shù)執(zhí)行前發(fā)生的事的一點(diǎn)總結(jié),限于水平,如有錯(cuò)誤請(qǐng)指正~

FAT 二進(jìn)制

FAT 二進(jìn)制文件,將多種架構(gòu)的 Mach-O 文件合并而成。它通過(guò) Fat Header 來(lái)記錄不同架構(gòu)在文件中的偏移量,F(xiàn)at Header 占一頁(yè)(64位16kb,32位4kb)的空間。
按分頁(yè)來(lái)存儲(chǔ)這些 segement 和 header 會(huì)浪費(fèi)空間,但這有利于虛擬內(nèi)存的實(shí)現(xiàn)。

image

Mach-O 文件

Mach-O為 Mach Object 文件格式的縮寫(xiě),它是一種用于可執(zhí)行文件,目標(biāo)代碼,動(dòng)態(tài)庫(kù),內(nèi)核轉(zhuǎn)儲(chǔ)的文件格式。
在 Mac OS X 系統(tǒng)中使用 Mach-O 作為其可執(zhí)行文件類(lèi)型。
它的組成結(jié)構(gòu)如下圖所示:

Mach-O 文件結(jié)構(gòu)

每個(gè) Mach-O 文件包括一個(gè) Mach-O Header,然后是一系列的載入命令 load commands,再是一個(gè)或多個(gè)段(segment),每個(gè)段包括0到255個(gè)節(jié)(section)。Mach-O使用REL再定位格式控制對(duì)符號(hào)的引用。Mach-O在兩級(jí)命名空間中將每個(gè)符號(hào)編碼成“對(duì)象-符號(hào)名”對(duì),在查找符號(hào)時(shí)則采用線性搜索法。

Mach-O包含了幾個(gè) segment,每個(gè) segment 又包含幾個(gè) section。segment的名字都是大寫(xiě)的,例如__DATA;section的名字都是小寫(xiě)的, 例如 __text。在 Mach-O 的類(lèi)型不為MH_OBJECT時(shí),空間大小為頁(yè)的整數(shù)倍。頁(yè)的大小跟硬件有關(guān),在 arm64 架構(gòu)一頁(yè)是16kb,其余為4kb。
section 雖然沒(méi)有整數(shù)倍頁(yè)大小的限制,但是 section 之間不會(huì)有重疊。

Mach-O Header

推薦使用MachOView這個(gè)軟件查看 Mach-O 的文件結(jié)構(gòu)。注意需要手動(dòng)關(guān)閉 processing,不然會(huì)閃退。下面是用 MachOView 查看自己的應(yīng)用結(jié)構(gòu):

使用MachOView查看文件結(jié)構(gòu)

東西有點(diǎn)多就沒(méi)有截取全部。我們查看一下Mach-O Header部分

Header結(jié)構(gòu)

下面是64位架構(gòu)下header的數(shù)據(jù)結(jié)構(gòu):

struct mach_header_64 {
    uint32_t    magic;      /* mach magic number identifier */
    cpu_type_t  cputype;    /* cpu specifier */
    cpu_subtype_t   cpusubtype; /* machine specifier */
    uint32_t    filetype;   /* type of file */
    uint32_t    ncmds;      /* number of load commands */
    uint32_t    sizeofcmds; /* the size of all the load commands */
    uint32_t    flags;      /* flags */
    uint32_t    reserved;   /* reserved */
};
image

Mach-O 全部的 filetype 和 flags在loader.h中找到。


除了MH_OBJECT以外的所有類(lèi)型,段(Segment)的空間大小均為頁(yè)的整數(shù)倍。頁(yè)的大小跟硬件有關(guān)系,在 arm64 架構(gòu)下一頁(yè)為16kb,其它為4kb。

Load commands

Load commands緊跟在頭部之后, 當(dāng)加載過(guò) header 之后,會(huì)通過(guò)解析Load commands來(lái)加載剩下的數(shù)據(jù),確定其內(nèi)存的分布。
下面是 load commands 的結(jié)構(gòu)定義:

struct load_command {
    uint32_t cmd;       /* 載入命令類(lèi)型 */
    uint32_t cmdsize;   /* total size of command in bytes */
};

所有load commands的大小即為 Header->sizeofcmds, 共有 Header->ncmds 條load command
load command 以LC開(kāi)頭,不同的加載命令有不同的專(zhuān)有的結(jié)構(gòu)體,cmd 和 cmdsize 是都有的,分別為命令類(lèi)型(即命令名稱(chēng)),這條命令的長(zhǎng)度。這些加載命令告訴系統(tǒng)應(yīng)該如何處理后面的二進(jìn)制數(shù)據(jù),對(duì)系統(tǒng)內(nèi)核加載器和動(dòng)態(tài)鏈接器起指導(dǎo)作用。如果當(dāng)前 LC_SEGMENT 包含 section,那么 section 的結(jié)構(gòu)體緊跟在 LC_SEGMENT 的結(jié)構(gòu)體之后,所占字節(jié)數(shù)由 SEGMENT 的 cmdsize 字段給出。

cmd(命令名稱(chēng)) 作用
LC_SEGMENT_64 將對(duì)應(yīng)的段中的數(shù)據(jù)加載并映射到進(jìn)程的內(nèi)存空間去
LC_SYMTAB 符號(hào)表信息
LC_DYSYMTAB 動(dòng)態(tài)符號(hào)表信息
LC_LOAD_DYLINKER 啟動(dòng)動(dòng)態(tài)加載連接器/usr/lib/dyld程序
LC_UUID 唯一的 UUID,標(biāo)示該二進(jìn)制文件,128bit
LC_VERSION_MIN_IPHONEOS/MACOSX 要求的最低系統(tǒng)版本(Xcode中的Deployment Target)
LC_MAIN 設(shè)置程序主線程的入口地址和棧大小
LC_ENCRYPTION_INFO 加密信息
LC_LOAD_DYLIB 加載的動(dòng)態(tài)庫(kù),包括動(dòng)態(tài)庫(kù)地址、名稱(chēng)、版本號(hào)等
LC_FUNCTION_STARTS 函數(shù)地址起始表
LC_CODE_SIGNATURE 代碼簽名信息

注意:不同類(lèi)型的 segment 會(huì)使用不同的函數(shù)來(lái)加載

Segment

Mach-O 文件中由許多個(gè)段(Segment),每個(gè)段都有不同的功能,每個(gè)段包含了許多個(gè)小的Section。LC_SEGMENT意味著這部分文件需要映射到進(jìn)程的地址空間去,幾乎所有 Mach-O 都包含這三個(gè)段:

  1. __TEXT:包含了執(zhí)行代碼和其它只讀數(shù)據(jù)(如C 字符串)。權(quán)限:只讀(VM_PROT_READ),可執(zhí)行(VM_PROT_EXECUTE)
  2. __DATA:程序數(shù)據(jù),包含全局變量,靜態(tài)變量等。權(quán)限:可讀寫(xiě)(VM_PROT_WRITE/READ) 可執(zhí)行(VM_PROT_EXECUTE)
  3. __LINKEDIT:包含了加載程序的"元數(shù)據(jù)",比如函數(shù)的名稱(chēng)和地址。權(quán)限:只讀(VM_PROT_READ)

除了上面三個(gè),還有一個(gè)常見(jiàn)的 segment:

  • __PAGEZERO:空指針陷阱段,映射到虛擬內(nèi)存空間的第一頁(yè),用于捕捉對(duì) NULL 指針的引用

LC_SEGMENT_64 的結(jié)構(gòu)定義為:

struct segment_command_64 { /* for 64-bit architectures */
    uint32_t    cmd;        /* LC_SEGMENT_64 */
    uint32_t    cmdsize;    /* includes sizeof section_64 structs */
    char        segname[16];    /* segment name */
    uint64_t    vmaddr;     /* memory address of this segment */
    uint64_t    vmsize;     /* memory size of this segment */
    uint64_t    fileoff;    /* file offset of this segment */
    uint64_t    filesize;   /* amount to map from the file */
    vm_prot_t   maxprot;    /* maximum VM protection */
    vm_prot_t   initprot;   /* initial VM protection */
    uint32_t    nsects;     /* number of sections in segment */
    uint32_t    flags;      /* flags */
};

可以看到這里大部分的成員變量都是幫助內(nèi)核將 segment 映射到虛擬內(nèi)存的。nsects即表明該段中包含多少個(gè) section,section是具體數(shù)據(jù)存放的地方。cmdsize表示當(dāng)前 segment 結(jié)構(gòu)體以及它所包含的所有 section 結(jié)構(gòu)體的總大小。

文件映射的起始位置由fileoff給出,映射到地址空間的vmaddr處。

Section

section 的名字均為小寫(xiě)。section 是具體數(shù)據(jù)存放的地方,它的結(jié)構(gòu)體跟隨在 LC_SEGMENT 結(jié)構(gòu)體之后。在64位環(huán)境中它的結(jié)構(gòu)定義為:

struct section_64 { /* for 64-bit architectures */
    char        sectname[16];   /* name of this section */
    char        segname[16];    /* segment this section goes in */
    uint64_t    addr;       /* memory address of this section */
    uint64_t    size;       /* size in bytes of this section */
    uint32_t    offset;     /* file offset of this section */
    uint32_t    align;      /* section alignment (power of 2) */
    uint32_t    reloff;     /* file offset of relocation entries */
    uint32_t    nreloc;     /* number of relocation entries */
    uint32_t    flags;      /* flags (section type and attributes)*/
    uint32_t    reserved1;  /* reserved (for offset or index) */
    uint32_t    reserved2;  /* reserved (for count or sizeof) */
    uint32_t    reserved3;  /* reserved */
};

其中flasg字段儲(chǔ)存了兩個(gè)屬性的值:type 和 attributes。type 只能有一個(gè)值,而 attributes 的值可以有多個(gè)。如果 segment 中任何一個(gè) section 擁有屬性 S_ATTR_DEBUG,那么該段所有的 section 都會(huì)擁有這個(gè)屬性。屬性詳情可以參考loader.h

section name 作用
__text 主程序代碼
__stub_helper 用于動(dòng)態(tài)鏈接的存根
__symbolstub1 用于動(dòng)態(tài)鏈接的存根
__objc_methname Objective-C 的方法名
__objc_classname Objective-C 的類(lèi)名
__cstring 硬編碼的字符串
__lazy_symbol 懶加載,延遲加載節(jié),通過(guò) dyld_stub_binder 輔助鏈接
_got 存儲(chǔ)引用符號(hào)的實(shí)際地址,類(lèi)似于動(dòng)態(tài)符號(hào)表
__nl_symbol_ptr 非延遲加載節(jié)
__mod_init_func 初始化的全局函數(shù)地址,在 main 之前被調(diào)用
__mod_term_func 結(jié)束函數(shù)地址
__cfstring Core Foundation 用到的字符串(OC字符串)
__objc_clsslist Objective-C 的類(lèi)列表
__objc_nlclslist Objective-C 的 +load 函數(shù)列表,比 __mod_init_func 更早執(zhí)行
__objc_const Objective-C 的常量
__data 初始化的可變的變量
__bss 未初始化的靜態(tài)變量

虛擬內(nèi)存

在 segment 的結(jié)構(gòu)體中,我們可以看到vmaddrvmsize兩個(gè)成員變量,它們分別代表 segment 在虛擬內(nèi)存中的地址以及大小。

虛擬內(nèi)存就是一層間接尋址(indirection)。軟件工程中有句格言就是任何問(wèn)題都能通過(guò)添加一個(gè)間接層來(lái)解決。虛擬內(nèi)存解決的是管理所有進(jìn)程使用物理 RAM 的問(wèn)題。通過(guò)添加間接層來(lái)讓每個(gè)進(jìn)程使用邏輯地址空間,它可以映射到 RAM 上的某個(gè)物理頁(yè)上。這種映射不是一對(duì)一的,邏輯地址可能映射不到 RAM 上,也可能有多個(gè)邏輯地址映射到同一個(gè)物理 RAM 上。針對(duì)第一種情況,當(dāng)進(jìn)程要存儲(chǔ)邏輯地址內(nèi)容時(shí)會(huì)觸發(fā) page fault;第二種情況就是多進(jìn)程共享內(nèi)存。

對(duì)于文件可以不用一次性讀入整個(gè)文件,可以使用分頁(yè)映射(mmap())的方式讀取。也就是把文件某個(gè)片段映射到進(jìn)程邏輯內(nèi)存的某個(gè)頁(yè)上。當(dāng)某個(gè)想要讀取的頁(yè)沒(méi)有在內(nèi)存中,就會(huì)觸發(fā) page fault,內(nèi)核只會(huì)讀入那一頁(yè),實(shí)現(xiàn)文件的懶加載。

也就是說(shuō) Mach-O 文件中的__TEXT段可以映射到多個(gè)進(jìn)程,并可以懶加載,且進(jìn)程之間共享內(nèi)存。__DATA段是可讀寫(xiě)的。這里使用到了Copy-On-Write技術(shù),簡(jiǎn)稱(chēng) COW。也就是多個(gè)進(jìn)程共享一頁(yè)內(nèi)存空間時(shí),一旦有進(jìn)程要做寫(xiě)操作,它會(huì)先將這頁(yè)內(nèi)存內(nèi)容復(fù)制一份出來(lái),然后重新映射邏輯地址到新的 RAM 頁(yè)上。也就是這個(gè)進(jìn)程自己擁有了那頁(yè)內(nèi)存的拷貝。這就涉及到了 clean/dirty page 的概念。dirty page 含有進(jìn)程自己的信息,而 clean page 可以被內(nèi)核重新生成(重新讀磁盤(pán))。所以 dirty page 的代價(jià)大于 clean page。

在多個(gè)進(jìn)程加載 Mach-O 文件時(shí)__TEXT__LINKEDIT因?yàn)橹蛔x,都是可以共享內(nèi)存的。而__DATA因?yàn)榭勺x寫(xiě),就會(huì)產(chǎn)生 dirty page。當(dāng) dyld 執(zhí)行結(jié)束后,__LINKEDIT就沒(méi)用了,對(duì)應(yīng)的內(nèi)存頁(yè)會(huì)被回收。

dyld

dyld

dyld(the dynamic link editor),Apple 的動(dòng)態(tài)鏈接器。在內(nèi)核完成映射進(jìn)程的工作后會(huì)啟動(dòng)dyld,負(fù)責(zé)加載應(yīng)用依賴的所有動(dòng)態(tài)鏈接庫(kù),準(zhǔn)備好運(yùn)行所需的一切。
在 App 啟動(dòng)的時(shí)候,首先會(huì)加載 App 的 mach-o 文件,從 load commands 中得到 dyld 的路徑,并且運(yùn)行。隨后 dyld 做的事情順序概括如下:

  1. 初始化運(yùn)行環(huán)境
  2. 加載主程序執(zhí)行文件 生成 image, 將image添加到一個(gè)全局容器中
  3. 加載共享緩存
  4. 根據(jù)依賴鏈遞歸加載動(dòng)態(tài)鏈接庫(kù) dylib,如果在緩存中有加載好的 image 則直接拿出來(lái),否則生成一個(gè)新的 image,將image添加到一個(gè)全局容器中
  5. link 主執(zhí)行文件
  6. link dylib
    • 根據(jù)依賴鏈遞歸修正指針 Rebase
    • 根據(jù)依賴鏈遞歸符號(hào)綁定 Bind
  7. 初始化 dylib(runtime 的初始化就在這個(gè)時(shí)候)

在加載完所有的 dylib 之后,它們處于互相獨(dú)立的狀態(tài),所以還需要將它們綁定起來(lái)。代碼簽名讓我們不能修改指令,所以不能直接讓一個(gè) dylib 調(diào)用另一個(gè) dylib,這時(shí)需要很多間接層。
這個(gè)時(shí)候需要 dyld 來(lái)修正指針(rebasing)和綁定符號(hào)(binding)。

詳細(xì)可以查看 dyld 的源碼中的_main函數(shù)。
下面會(huì)分析上述的其中幾個(gè)步驟。

ImageLoader

ImageLoader是一個(gè)將 mach-o 文件里面二進(jìn)制數(shù)據(jù)(編譯過(guò)的代碼、符號(hào)等)加載到內(nèi)存的基類(lèi),它負(fù)責(zé)將 mach-o 中的二進(jìn)制數(shù)據(jù)映射到內(nèi)存,它的實(shí)例就是我們熟悉的 image。
每一個(gè) mach-o 文件都會(huì)有一個(gè)對(duì)應(yīng)的 image,實(shí)例的類(lèi)型根據(jù) mach-o 格式的不同也會(huì)不同。

image
  • ImageLoaderMachOClassic: 用于加載__LINKEDIT段為傳統(tǒng)格式的 mach-o 文件
  • ImageLoaderMachOCompressed: 用于加載__LINKEDIT段為壓縮格式的 mach-o 文件

因?yàn)?code>dylib之間有依賴關(guān)系,所以系統(tǒng)會(huì)沿著依賴鏈遞歸加載 image。

Rebasing

dylib的二進(jìn)制數(shù)據(jù)會(huì)隨機(jī)的映射到內(nèi)存的一個(gè)隨機(jī)地址ASLR(Address space layout randomization,)中,這個(gè)隨機(jī)的地址跟代碼和數(shù)據(jù)指向的舊地址(preferred_address)會(huì)有一定的偏差,dyld需要修正這個(gè)偏差(slide),做法就是將dylib內(nèi)部的指針地址都加上這個(gè)偏移值,偏移值的計(jì)算方法如下:

slide = actual_address - preferred_address

隨后就是不斷的將__DATA段中需要修正的指針加上這個(gè)偏移值。
注意:每次程序啟動(dòng)后的地址都會(huì)變化,所以指針的地址都需要重新修正。

在 mach-o 的一個(gè)載入命令LC_DYLD_INFO_ONLY可以查看到rebase, bind, week_bind,lazy_bind的偏移量和大小。

image

Binding

binding處理那些指向dylib外部的指針,它們實(shí)際上被符號(hào)名稱(chēng)(symbol)綁定,也就是個(gè)字符串。比如我們 objc 代碼中需要使用到 NSObject, 即符號(hào)OBJC_CLASS$_NSObject,但是這個(gè)符號(hào)不存在當(dāng)前的 image 中,而是在系統(tǒng)庫(kù) Foundation.framework中,因此就需要binding這個(gè)操作將對(duì)應(yīng)關(guān)系綁定到一起。

Lazy Binding

lazyBinding就是在加載動(dòng)態(tài)庫(kù)的時(shí)候不會(huì)立即 binding, 當(dāng)時(shí)當(dāng)?shù)谝淮握{(diào)用這個(gè)方法的時(shí)候再實(shí)施 binding。 做到的方法也很簡(jiǎn)單: 通過(guò)dyld_stub_binder這個(gè)符號(hào)來(lái)做。lazy binding 的方法第一次會(huì)調(diào)用到 dyld_stub_binder, 然后 dyld_stub_binder負(fù)責(zé)找到真實(shí)的方法,并且將地址bind到樁上,下一次就不用再bind了。
多數(shù)符號(hào)都是 lazy binding 的

Runtime

每一個(gè)dylib都有自己的初始化方法,當(dāng)相應(yīng)的 image 被加載到內(nèi)存后,就會(huì)調(diào)用初始化方法。當(dāng)然這不是調(diào)用名為initialize方法,而是C++靜態(tài)對(duì)象初始化構(gòu)造器,__attribute__((constructor))標(biāo)記的方法以及Initializer方法。你可以在程序中設(shè)置環(huán)境變量DYLD_PRINT_INITIALIZERS來(lái)打印dylib的初始化方法。

image
打印信息

我們可以看到程序首先調(diào)用了libSystem這個(gè)dylib的初始化方法。libSystem是很多系統(tǒng)的lib的集合,包括 libdispatch(GCD), libsystem_c(c語(yǔ)言庫(kù)), libsystem_blocks(block)。
libSystem的源碼init.c中我們可以看到,它的初始化方法libSystem_initializer會(huì)調(diào)用libdispatch_init();, 然后逐步調(diào)用到_objc_init,也就是 objc 和 runtime 的入口。
添加一個(gè)符號(hào)斷點(diǎn)_objc_init,下面是方法調(diào)用棧:

斷點(diǎn)調(diào)試

注意:runtime 和 objc 屬于libobjc


下面是_objc_init的實(shí)現(xiàn):

void _objc_init(void)
{
    // 省略...
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

上面的map_images不是將 image 添加到內(nèi)存中的意思,在這個(gè)方法被調(diào)用的時(shí)候,已經(jīng)完成了 image 的映射以及指針修正,綁定符號(hào)的工作了。
這個(gè)函數(shù)實(shí)際上是將 image 中 OBJC 相關(guān)的信息進(jìn)行初始化,具體實(shí)現(xiàn)可以查看_read_image的源碼,因?yàn)榇a太多所以這里就不貼出來(lái)了,下面是具體做的事情:

  • 會(huì)將所有的 Class 存放在一張映射類(lèi)名與 Class 的全局表中gdb_objc_realized_classes
  • 隨后調(diào)用readClass函數(shù)將 每一個(gè) Class 添加到gdb_objc_realized_classes表中。
  • 確定 selector 是唯一的
  • read protocols: 讀取protocol
  • realizeClasses:這一步的意義就是動(dòng)態(tài)鏈接好class, 讓class處于可用狀態(tài),主要操作如下:
    • 檢查ro是否已經(jīng)替換為rw,沒(méi)有就替換一下。
    • 檢查類(lèi)的父類(lèi)和metaClass是否已經(jīng)realize,沒(méi)有就先把它們先realize
    • 重新layout ivar. 因?yàn)橹挥屑虞d好了所有父類(lèi),才能確定ivar layout
    • 把一些flags從ro拷貝到rw
    • 鏈接class的 nextSiblingClass 鏈表
    • attach categories: 合并categories的method list、 properties、protocols到 class_rw_t 里面
  • read categories:讀取類(lèi)目

map_images結(jié)束會(huì)調(diào)用load_images函數(shù)。這一步做的事情比較少,就是調(diào)用我們熟悉的+(load)函數(shù)。父類(lèi)會(huì)先調(diào)用,除了 Class,每個(gè)類(lèi)目的+(load)方法也會(huì)被調(diào)用一次,但順序就不一定了。

總結(jié)

在這里對(duì) main 函數(shù)之前的操作做一個(gè)小總結(jié)吧:

  1. 將 App 的 mach-o header 讀取到內(nèi)存中
  2. 根據(jù) load commands 獲取 dyld 的路徑,運(yùn)行 dyld
  3. 初始化運(yùn)行環(huán)境,加載 dylib,如果緩存中存在則從緩存中拿出加載過(guò)的 image,否則新建一個(gè) image,加載到內(nèi)存中
  4. 修正指針(rebase),綁定符號(hào)(bind)
  5. 初始化 dylib,運(yùn)行 runtime
  6. runtime 將 image 中有關(guān) OBJC 的數(shù)據(jù)進(jìn)行初始化
  7. 調(diào)用 +(load) 方法
  8. dyld 調(diào)用 main 函數(shù)

花了一周的時(shí)間用來(lái)研究這部分的內(nèi)容,終于填完坑了~很舒服
最大的感受就是學(xué)習(xí)完后,看 clang 編譯后的 C++ 代碼能看懂的更多了。比如添加完一個(gè)類(lèi)目之后,會(huì)將這個(gè)這個(gè)類(lèi)目添加到__DATA的section __objc_catlist中,以前不知道啥意思現(xiàn)在就明白了。也明白 xcode 的許多設(shè)置是用來(lái)干嘛的,總之好處多多~
學(xué)習(xí)也是一個(gè)遞歸的過(guò)程,總之,也多加油吧!

引用

iOS 程序 main 函數(shù)之前發(fā)生了什么
優(yōu)化 App 的啟動(dòng)時(shí)間
dyld源碼分析-動(dòng)態(tài)加載load
dyld與ObjC

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,179評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 175,628評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,642評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,444評(píng)論 6 405
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 54,948評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,185評(píng)論 0 287
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,717評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,602評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,794評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,045評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,418評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,671評(píng)論 1 281
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,414評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,750評(píng)論 2 370

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

  • 背景 一個(gè)項(xiàng)目做的時(shí)間長(zhǎng)了,啟動(dòng)流程往往容易雜亂,庫(kù)也用的越來(lái)越多,APP的啟動(dòng)時(shí)間也會(huì)慢慢變長(zhǎng)。本次將針對(duì)iOS...
    醬油瓶2閱讀 3,526評(píng)論 0 12
  • 關(guān)鍵時(shí)刻,第一時(shí)間送達(dá)! 問(wèn)題種類(lèi) 時(shí)間復(fù)雜度 在集合里數(shù)據(jù)量小的情況下時(shí)間復(fù)雜度對(duì)于性能的影響看起來(lái)微乎其微。但...
    C9090閱讀 901評(píng)論 0 1
  • 這是一篇 WWDC 2016 Session 406 的學(xué)習(xí)筆記,從原理到實(shí)踐講述了如何優(yōu)化 App 的啟動(dòng)時(shí)間。...
    MTDeveloper閱讀 751評(píng)論 0 1
  • 這是一篇 WWDC 2016 Session 406 的學(xué)習(xí)筆記,從原理到實(shí)踐講述了如何優(yōu)化 App 的啟動(dòng)時(shí)間。...
    茗涙閱讀 1,874評(píng)論 0 3
  • 文/村草 上次出去玩回來(lái)的路上,跟超子討論一個(gè)話題,“現(xiàn)在有什么好項(xiàng)目可以投資,并且是投資小見(jiàn)效快的?” 最關(guān)鍵的...
    村草視角閱讀 328評(píng)論 2 6