iOS的App啟動(dòng)詳細(xì)過程,看這篇就夠了

虛擬內(nèi)存 & ASLR

在早期計(jì)算機(jī)中數(shù)據(jù)是直接通過物理地址訪問的,這就造成了下面兩個(gè)問題

1.內(nèi)存不夠用
2.數(shù)據(jù)安全問題

內(nèi)存不夠 ---> 虛擬內(nèi)存

虛擬內(nèi)存就是通過創(chuàng)建一張物理地址和虛擬地址的映射表來管理內(nèi)存,提高了CPU利用率空凸,使多個(gè)進(jìn)程可以同時(shí)/按需加載

  • 在iOS中蜕便,每個(gè)進(jìn)程都有獨(dú)立的虛擬內(nèi)存劫恒,存放物理內(nèi)存中,其地址是從0開始的轿腺,大小固定4G两嘴,每個(gè)虛擬內(nèi)存又會(huì)按頁(yè)劃分,每頁(yè)16K族壳,以頁(yè)為單位加載憔辫,每個(gè)進(jìn)程是相互獨(dú)立的,保證進(jìn)程間的數(shù)據(jù)安全

  • 當(dāng)一個(gè)進(jìn)程只有部分功能使用時(shí)仿荆,系統(tǒng)會(huì)自動(dòng)將使用部分加載到物理內(nèi)存中

  • CPU在進(jìn)行數(shù)據(jù)訪問時(shí)贰您,會(huì)先訪問虛擬內(nèi)存,通過虛擬內(nèi)存和物理內(nèi)存的映射關(guān)系拢操,去對(duì)應(yīng)的物理內(nèi)存中查找锦亦,在CPU上有專門處理映射的硬件

  • 當(dāng)CPU訪問數(shù)據(jù)時(shí),如果虛擬內(nèi)存中的數(shù)據(jù)沒有物理內(nèi)存綁定令境,會(huì)發(fā)生缺頁(yè)異常(pagefault)杠园,會(huì)阻塞當(dāng)前進(jìn)程,直到數(shù)據(jù)加載到物理內(nèi)存中舔庶,虛擬內(nèi)存和物理內(nèi)存綁定

  • 當(dāng)內(nèi)存條中的內(nèi)存全部用完后抛蚁,系統(tǒng)會(huì)將新的數(shù)據(jù)覆蓋到很久沒使用的內(nèi)存上

數(shù)據(jù)安全 ---> ASLR

因?yàn)樘摂M內(nèi)存的起始地址(0x000000)和大小(4G)是固定的,這就意味著我們的數(shù)據(jù)地址也是固定的惕橙,所以在iOS4.3引進(jìn)了ASLR瞧甩。
ASLR:英文全稱Address Space Layout Randomization,也叫地址空間配置隨機(jī)加載弥鹦,是一種針對(duì)緩沖區(qū)溢出安全保護(hù)技術(shù)肚逸。通過對(duì)堆、棧惶凝、共享庫(kù)映射等線性區(qū)布局的隨機(jī)化吼虎,增強(qiáng)了攻擊者找到目標(biāo)物理內(nèi)存的難度,防止攻擊者直接定位攻擊代碼位置苍鲜,達(dá)到阻止溢出攻擊的目的的一種技術(shù)。
由于ASLR的存在玷犹,導(dǎo)致可執(zhí)行文件和動(dòng)態(tài)鏈接庫(kù)在虛擬內(nèi)存中的地址每次都是不固定的混滔,所以需要在編譯時(shí)來修復(fù)鏡像中的資源指針洒疚,來指向正確的地址。即正確的內(nèi)存地址 = ASLR地址 + 偏移值

iOS 系統(tǒng)架構(gòu)

Mac系統(tǒng)是基于Unix內(nèi)核的圖形化操作系統(tǒng)坯屿。Mac OS 和 iOS 系統(tǒng)架構(gòu)的對(duì)比分析發(fā)現(xiàn)油湖,Mac OS和iOS的系統(tǒng)架構(gòu)層次只有最上面一層不同,Mac是Cocoa框架领跛,而iOS是Cocoa Touch框架乏德,其余的架構(gòu)層次都是一樣的。


Core OS是用FreeBSD和Mach所改寫的一個(gè)名叫Darwin的開放原始碼操作系統(tǒng), 是開源吠昭、符合POSIX標(biāo)準(zhǔn)的一個(gè)Unix核心喊括。這一層包含并提供了整個(gè)iPhone OS的一些基礎(chǔ)功能,比如:硬件驅(qū)動(dòng), 內(nèi)存管理矢棚,程序管理郑什,線程管理(POSIX),文件系統(tǒng)蒲肋,網(wǎng)絡(luò)(BSD Socket),以及標(biāo)準(zhǔn)輸入輸出等蘑拯,所有這些功能都會(huì)通過C語(yǔ)言的API來提供。


核心OS層的驅(qū)動(dòng)提供了硬件和系統(tǒng)框架之間的接口兜粘。然而申窘,由于安全的考慮,只有有限的系統(tǒng)框架類能訪問內(nèi)核和驅(qū)動(dòng)孔轴。iPhone OS提供了許多訪問操作系統(tǒng)低層功能的接口集剃法,iPhone 應(yīng)用通過LibSystem庫(kù)來訪問這些功能,這些接口集有線程(POSIX線程)距糖、網(wǎng)絡(luò)(BSD sockets)玄窝、文件系統(tǒng)訪問、標(biāo)準(zhǔn)I/O悍引、Bonjour和DNS服務(wù)恩脂、現(xiàn)場(chǎng)信息(Locale Information)、內(nèi)存分配和數(shù)學(xué)計(jì)算等趣斤。

Core Services在Core OS基礎(chǔ)上提供了更為豐富的功能俩块, 它包含了Foundation.Framework和Core Foundation.Framework。之所以叫Foundation浓领,就是因?yàn)樗峁┝艘幌盗刑幚碜址窨帕小⒔M合联贩、日歷漫仆、時(shí)間等等的基本功能。

Foundation是屬于Objective-C的API泪幌,Core Fundation是屬于C的API盲厌。另外Core servieces還提供了如Security(用來處理認(rèn)證署照,密碼管理,安全性管理等), Core Location, SQLite和Address Book等功能吗浩。

核心基礎(chǔ)框架(CoreFoundation.framework)是基于C語(yǔ)言的接口集建芙,提供iPhone應(yīng)用的基本數(shù)據(jù)管理和服務(wù)功能。該框架支持Collection數(shù)據(jù)類型(Arrays懂扼、 Sets等)禁荸、Bundles、字符串管理阀湿、日期和時(shí)間管理赶熟、原始數(shù)據(jù)塊管理、首選項(xiàng)管理炕倘、URL和Stream操作钧大、線程和運(yùn)行循環(huán)(Run Loops)、端口和Socket通信罩旋。

核心基礎(chǔ)框架與基礎(chǔ)框架是緊密相關(guān)的啊央,它們?yōu)橄嗤幕竟δ芴峁┝薕bjective-C接口。如果開發(fā)者混合使用Foundation Objects 和Core Foundation類型涨醋,就能充分利用存在兩個(gè)框架中的"toll-free bridging"技術(shù)(橋接)瓜饥。toll-free bridging使開發(fā)者能使用這兩個(gè)框架中的任何一個(gè)的核心基礎(chǔ)和基礎(chǔ)類型。

程序啟動(dòng)之前

從應(yīng)用圖標(biāo)被用戶點(diǎn)擊開始浴骂,直到應(yīng)用可以開始響應(yīng)發(fā)生了很多事情乓土。


很多文章中大家都提到說dyld加載了主程序和動(dòng)態(tài)庫(kù),這個(gè)理解明顯是錯(cuò)誤的溯警,在XNU加載Mach-O和dyld過程中趣苏,是內(nèi)核加載了主程序,dyld只會(huì)負(fù)責(zé)動(dòng)態(tài)庫(kù)的加載梯轻。雖然主程序也會(huì)作為鏡像形式被dyld來管理起來食磕。當(dāng)一個(gè)App啟動(dòng)時(shí),dyld把App需要的dylib加載進(jìn)App的內(nèi)存空間喳挑。App運(yùn)行所需要的信息拘央,一般都存放在其MachO頭部44中持舆,其中dylib的信息是由Load Commands指定的沼沈,App得到執(zhí)行時(shí)傲武,dyld會(huì)查看其MachO頭部中的Load Commands,并把里面LC_LOAD_DYLIB相關(guān)的dylib給加載到進(jìn)程的內(nèi)存空間曹宴。

一般來說搂橙,逆向工程會(huì)在dyld階段入手,之前版本的dyld中確實(shí)存在一些漏洞笛坦,使App能夠繞過代碼簽名份氧,例如dyld-353.2.1版本唯袄,漏洞編號(hào)CVE-2015-5876弯屈,漏洞存在于Mach-O頭的處理過程中蜗帜,一個(gè)畸形的Mach-O文件可以導(dǎo)致內(nèi)存段被替換,從而導(dǎo)致任意代碼執(zhí)行资厉,當(dāng)然目前dyld的已知漏洞都已修復(fù)厅缺。監(jiān)控啟動(dòng)崩潰會(huì)在Objc階段之后,具體方法具體分析宴偿。

總結(jié)來說湘捎,大體分為如下步驟:

(1) 系統(tǒng)為程序啟動(dòng)做好準(zhǔn)備。
(2) 系統(tǒng)將控制權(quán)交給 Dyld窄刘,Dyld 會(huì)負(fù)責(zé)后續(xù)的工作窥妇。
(3) Dyld 加載程序所需的動(dòng)態(tài)庫(kù)。
(3) Dyld 對(duì)程序進(jìn)行 rebase 以及 bind 操作娩践。
(4) Objc SetUp活翩。
(5) 運(yùn)行初始化函數(shù)。
(6) 執(zhí)行程序的 main 函數(shù)翻伺。

需要注意的是材泄,dyld2和dyld3的加載方式略有不同。dyld2是純粹的in-process吨岭,也就是在程序進(jìn)程內(nèi)執(zhí)行的拉宗,也就意味著只有當(dāng)應(yīng)用程序被啟動(dòng)的時(shí)候,dyld2才能開始執(zhí)行任務(wù)辣辫。dyld3則是部分out-of-process旦事,部分in-process。

dyld2的過程是:加載dyld到App進(jìn)程急灭,加載動(dòng)態(tài)庫(kù)(包括所依賴的所有動(dòng)態(tài)庫(kù))姐浮,Rebase,Bind化戳,初始化Objective C Runtime和其它的初始化代碼单料。

dyld3的out-of-process會(huì)做如下事情:分析Mach-O Headers,分析依賴的動(dòng)態(tài)庫(kù)点楼,查找需要Rebase & Bind之類的符號(hào)扫尖,把上述結(jié)果寫入緩存。這樣掠廓,在應(yīng)用啟動(dòng)的時(shí)候换怖,就可以直接從緩存中讀取數(shù)據(jù),加快加載速度蟀瞧。

從exec()開始

Mach-O是 OS X 系統(tǒng)的可執(zhí)行文件沉颂,Mach-O有多種文件類型条摸,比如MH_DYLIB文件、MH_BUNDLE文件铸屉、MH_EXECUTE文件钉蒲,MH_OBJECT(內(nèi)核加載)等〕固常可執(zhí)行文件離不開進(jìn)程顷啼,在 Linux 中,我們會(huì)通過 Fork()來新創(chuàng)建子進(jìn)程昌屉,然后執(zhí)行鏡像通過exec()來替換為另一個(gè)可執(zhí)行程序钙蒙。在用戶態(tài)會(huì)通過exec*系列函數(shù)來加載一個(gè)可執(zhí)行文件。

main()函數(shù)是整個(gè)程序的入口间驮,在程序啟動(dòng)之前躬厌,系統(tǒng)會(huì)調(diào)用exec()函數(shù)。在Unix中exec和system的不同在于竞帽,system是用shell來調(diào)用程序扛施,相當(dāng)于fork+exec+waitpid,fork 函數(shù)創(chuàng)建子進(jìn)程后通常都會(huì)調(diào)用 exec 函數(shù)來執(zhí)行一個(gè)新程序抢呆;而exec是直接讓你的程序代替原來的程序運(yùn)行煮嫌。

system 是在單獨(dú)的進(jìn)程中執(zhí)行命令,完了還會(huì)回到你的程序中抱虐。而exec函數(shù)是直接在你的進(jìn)程中執(zhí)行新的程序昌阿,新的程序會(huì)把你的程序覆蓋,除非調(diào)用出錯(cuò)恳邀,否則你再也回不到exec后面的代碼懦冰,也就是當(dāng)前的程序變成了exec調(diào)用的那個(gè)程序了。

UNIX 提供了 6 種不同的 exec 函數(shù)供我們使用谣沸。

#include <unistd.h>
int execl(const char *pathname, const char *arg0, ... /* (char *)0 */);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ... /* (char *)0, char *const envp[] */);
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execlp(const char *filename, const char *arg0, ... /* (char *)0 */);
int execvp(cosnt char *filename, char *const argv[]);

通過分析我們發(fā)現(xiàn)刷钢,含有 l 和 v 的 exec 函數(shù)的參數(shù)表傳遞方式是不同的。含有 e 結(jié)尾的 exec 函數(shù)會(huì)傳遞一個(gè)環(huán)境變量列表乳附。含有 p 結(jié)尾的 exec 函數(shù)取的是新程序的文件名作為參數(shù)内地,而其他exec 函數(shù)取的是新程序的路徑。

如果函數(shù)出錯(cuò)則返回-1赋除,若成功則沒有返回值阱缓。其中只有execve是真正意義上的系統(tǒng)調(diào)用,其它都是在此基礎(chǔ)上經(jīng)過包裝的庫(kù)函數(shù)举农。

exec函數(shù)族的作用是根據(jù)指定的文件名找到可執(zhí)行文件荆针,并用它來取代調(diào)用進(jìn)程的內(nèi)容,換句話說,就是在調(diào)用進(jìn)程內(nèi)部執(zhí)行一個(gè)可執(zhí)行文件航背。這里的可執(zhí)行文件既可以是二進(jìn)制文件喉悴,也可以是任何Unix下可執(zhí)行的腳本文件。

Dyld

Dyld 是 iOS 系統(tǒng)的動(dòng)態(tài)鏈接器玖媚, 在dyldStartup.s 文件中有個(gè)名為 __dyld_start 的方法箕肃,它會(huì)去調(diào)用 dyldbootstrap::start() 方法,然后進(jìn)一步調(diào)用 dyld::_main() 方法最盅,里面包含 App 的整個(gè)啟動(dòng)流程突雪,該函數(shù)最終返回應(yīng)用程序 main 函數(shù)的地址,最后 Dyld 會(huì)去調(diào)用它涡贱。

之后會(huì)去加載可執(zhí)行文件,二進(jìn)制文件常被稱為 image惹想,包括可執(zhí)行文件问词、動(dòng)態(tài)庫(kù)等,ImageLoader 的作用就是將二進(jìn)制文件加載進(jìn)內(nèi)存嘀粱。dyld::_main() 方法在設(shè)置好運(yùn)行環(huán)境后激挪,會(huì)調(diào)用instantiateFromLoadedImage 函數(shù)將可執(zhí)行文件加載進(jìn)內(nèi)存中,加載過程分為三步:

1.合法性檢查锋叨。主要是檢查可執(zhí)行文件是否合法垄分,是否能在當(dāng)前的 CPU 架構(gòu)下運(yùn)行。
2.選擇 ImageLoader 加載可執(zhí)行文件娃磺。系統(tǒng)會(huì)去判斷可執(zhí)行文件的類型薄湿,選擇相應(yīng)的 ImageLoader 將其加載進(jìn)內(nèi)存空間中。
3.注冊(cè) image 信息偷卧〔蛄觯可執(zhí)行文件加載完成后,系統(tǒng)會(huì)調(diào)用 addImage 函數(shù)將其管理起來听诸,并更新內(nèi)存分布信息坐求。

以上三步完成后,Dyld 會(huì)調(diào)用 link 函數(shù)開始之后的處理流程晌梨。

靜態(tài)鏈接庫(kù)與動(dòng)態(tài)鏈接庫(kù)

iOS中的相關(guān)文件有如下幾種:Dylib桥嗤,動(dòng)態(tài)鏈接庫(kù)(又稱 DSO 或 DLL);Bundle仔蝌,不能被鏈接的 Dylib泛领,只能在運(yùn)行時(shí)使用 dlopen() 加載,可當(dāng)做 macOS 的插件掌逛。Framework师逸,包含 Dylib 以及資源文件和頭文件的文件夾。

動(dòng)態(tài)鏈接庫(kù)是一組源代碼的模塊,每個(gè)模塊包含一些可供應(yīng)用程序或者其他動(dòng)態(tài)鏈接庫(kù)調(diào)用的函數(shù)篓像,在應(yīng)用程序調(diào)用一個(gè)動(dòng)態(tài)鏈接庫(kù)里面的函數(shù)的時(shí)候动知,操作系統(tǒng)會(huì)將動(dòng)態(tài)鏈接庫(kù)的文件映像映射到進(jìn)程的地址空間中,這樣進(jìn)程中所有的線程就可以調(diào)用動(dòng)態(tài)鏈接庫(kù)中的函數(shù)了员辩。動(dòng)態(tài)鏈接庫(kù)加載完成后盒粮,這個(gè)時(shí)候動(dòng)態(tài)鏈接庫(kù)對(duì)于進(jìn)程中的線程來說只是一些被放在地址進(jìn)程空間附加的代碼和數(shù)據(jù),操作系統(tǒng)為了節(jié)省內(nèi)存空間奠滑,同一個(gè)動(dòng)態(tài)鏈接庫(kù)在內(nèi)存中只有一個(gè)丹皱,操作系統(tǒng)也只會(huì)加載一次到內(nèi)存中。

因?yàn)榇a段在內(nèi)存中的權(quán)限都是為只讀的宋税,所以當(dāng)多個(gè)應(yīng)用程序加載同一個(gè)動(dòng)態(tài)鏈接庫(kù)的時(shí)候摊崭,不用擔(dān)心應(yīng)用程序會(huì)修改動(dòng)態(tài)鏈接庫(kù)的代碼段。當(dāng)線程調(diào)用動(dòng)態(tài)鏈接庫(kù)的一個(gè)函數(shù)杰赛,函數(shù)會(huì)在線程棧中取得傳遞給他的參數(shù)呢簸,并使用線程棧來存放他需要的變量,動(dòng)態(tài)鏈接庫(kù)函數(shù)創(chuàng)建的任何對(duì)象都為調(diào)用線程或者調(diào)用進(jìn)程擁有乏屯,動(dòng)態(tài)鏈接庫(kù)不會(huì)擁有任何對(duì)象根时。如果動(dòng)態(tài)鏈接庫(kù)中的一個(gè)函數(shù)調(diào)用了VirtualAlloc,系統(tǒng)會(huì)從調(diào)用進(jìn)程的地址空間預(yù)定地址辰晕,即使撤銷了對(duì)動(dòng)態(tài)鏈接庫(kù)的映射蛤迎,調(diào)用進(jìn)程的預(yù)定地址依然會(huì)存在,直到用戶取消預(yù)定或者進(jìn)程結(jié)束含友。

靜態(tài)鏈接庫(kù)與動(dòng)態(tài)鏈接庫(kù)都是共享代碼的方式替裆,如果采用靜態(tài)鏈接庫(kù),則無論你愿不愿意唱较,lib 中的指令都全部被直接包含在最終生成的包文件中了扎唾。但是若使用動(dòng)態(tài)鏈接庫(kù),該動(dòng)態(tài)鏈接庫(kù)不必被包含在最終包里南缓,包文件執(zhí)行時(shí)可以“動(dòng)態(tài)”地引用和卸載這個(gè)與安裝包獨(dú)立的動(dòng)態(tài)鏈接庫(kù)文件胸遇。靜態(tài)鏈接庫(kù)和動(dòng)態(tài)鏈接庫(kù)的另外一個(gè)區(qū)別在于靜態(tài)鏈接庫(kù)中不能再包含其他的動(dòng)態(tài)鏈接庫(kù)或者靜態(tài)庫(kù),而在動(dòng)態(tài)鏈接庫(kù)中還可以再包含其他的動(dòng)態(tài)或靜態(tài)鏈接庫(kù)汉形。

Linux中靜態(tài)函數(shù)庫(kù)的名字一般是libxxx.a纸镊,利用靜態(tài)函數(shù)庫(kù)編譯成的文件比較大,因?yàn)檎麄€(gè)函數(shù)庫(kù)的所有數(shù)據(jù)都會(huì)被整合進(jìn)目標(biāo)代碼中概疆。編譯后的執(zhí)行程序不需要外部的函數(shù)庫(kù)支持逗威,因?yàn)樗惺褂玫暮瘮?shù)都已經(jīng)被編譯進(jìn)去了。當(dāng)然這也會(huì)成為他的缺點(diǎn)岔冀,因?yàn)槿绻o態(tài)函數(shù)庫(kù)改變了凯旭,那么你的程序必須重新編譯。

動(dòng)態(tài)函數(shù)庫(kù)的名字一般是libxxx.so,相對(duì)于靜態(tài)函數(shù)庫(kù)罐呼,動(dòng)態(tài)函數(shù)庫(kù)在編譯的時(shí)候并沒有被編譯進(jìn)目標(biāo)代碼中鞠柄,你的程序執(zhí)行到相關(guān)函數(shù)時(shí)才調(diào)用該函數(shù)庫(kù)里的相應(yīng)函數(shù),因此動(dòng)態(tài)函數(shù)庫(kù)所產(chǎn)生的可執(zhí)行文件比較小嫉柴。由于函數(shù)庫(kù)沒有被整合進(jìn)你的程序厌杜,而是程序運(yùn)行時(shí)動(dòng)態(tài)的申請(qǐng)并調(diào)用,所以程序的運(yùn)行環(huán)境中必須提供相應(yīng)的庫(kù)计螺。動(dòng)態(tài)函數(shù)庫(kù)的改變并不影響你的程序夯尽,所以動(dòng)態(tài)函數(shù)庫(kù)的升級(jí)比較方便。

iOS開發(fā)中靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)是相對(duì)編譯期和運(yùn)行期的登馒。靜態(tài)庫(kù)在程序編譯時(shí)會(huì)被鏈接到目標(biāo)代碼中匙握,程序運(yùn)行時(shí)將不再需要載入靜態(tài)庫(kù)。而動(dòng)態(tài)庫(kù)在程序編譯時(shí)并不會(huì)被鏈接到目標(biāo)代碼中谊娇,只是在程序運(yùn)行時(shí)才被載入肺孤,因?yàn)樵诔绦蜻\(yùn)行期間還需要?jiǎng)討B(tài)庫(kù)的存在。

iOS中靜態(tài)庫(kù)可以用.a或.Framework文件表示济欢,動(dòng)態(tài)庫(kù)的形式有.dylib和.framework。系統(tǒng)的.framework是動(dòng)態(tài)庫(kù)小渊,一般自己建立的.framework是靜態(tài)庫(kù)法褥。

.a是一個(gè)純二進(jìn)制文件,.framework中除了有二進(jìn)制文件之外還有資源文件酬屉。.a文件不能直接使用半等,至少要有.h文件配合。.framework文件可以直接使用呐萨,.a + .h + sourceFile = .framework杀饵。

動(dòng)態(tài)庫(kù)的一個(gè)重要特性就是即插即用性,我們可以選擇在需要的時(shí)候再加載動(dòng)態(tài)庫(kù)谬擦。如果不希望在軟件一啟動(dòng)就加載動(dòng)態(tài)庫(kù)切距,需要將

Targets-->Build Phases-->Link Binary With Libraries

中 *.framework 對(duì)應(yīng)的Status由默認(rèn)的 Required 改成 Optional ;或者將 xx.framework 從 Link Binary With Libraries 列表中刪除惨远。

可以使用dlopen加載動(dòng)態(tài)庫(kù)谜悟,動(dòng)態(tài)庫(kù)中真正的可執(zhí)行代碼為 xx.framework/xx 文件。

- (IBAction)useDlopenLoad:(id)sender
{
NSString *documentsPath = [NSString stringWithFormat:@"%@/Documents/xx.framework/xx",NSHomeDirectory()];
[self dlopenLoadlib:documentsPath];
}

- (void)dlopenLoadlib:(NSString *)path
{
libHandle = NULL;
libHandle = dlopen([path cStringUsingEncoding:NSUTF8StringEncoding], RTLD_NOW);
if (libHandle == NULL) {
    char *error = dlerror();
    NSLog(@"dlopen error: %s", error);
} else {
    NSLog(@"dlopen load framework success.");
}
}

也可以使用NSBundle來加載動(dòng)態(tài)庫(kù)北秽,實(shí)現(xiàn)代碼如下:

- (IBAction)useBundleLoad:(id)sender
{
NSString *documentsPath = [NSString stringWithFormat:@"%@/Documents/xx.framework",NSHomeDirectory()];
[self bundleLoadlib:documentsPath];
}

- (void)bundleLoadlib:(NSString *)path
{
_libPath = path;
NSError *err = nil;
NSBundle *bundle = [NSBundle bundleWithPath:path];
if ([bundle loadAndReturnError:&err]) {
    NSLog(@"bundle load framework success.");
} else {
    NSLog(@"bundle load framework err:%@",err);
}
}

可以為動(dòng)態(tài)庫(kù)的加載和移除添加監(jiān)聽回調(diào)葡幸,ImageLogger上有一個(gè)完整的示例代碼,從中可以發(fā)現(xiàn)贺氓,一個(gè)工程軟件啟動(dòng)的時(shí)候會(huì)加載多達(dá)一百二十多個(gè)動(dòng)態(tài)庫(kù)蔚叨,即使是一個(gè)空白的項(xiàng)目。

但是,需要注意的一點(diǎn)是蔑水,不要在初始化方法中調(diào)用 dlopen()邢锯,對(duì)性能有影響。因?yàn)?dyld 在 App 開始前運(yùn)行肤粱,由于此時(shí)是單線程運(yùn)行所以系統(tǒng)會(huì)取消加鎖弹囚,但 dlopen() 開啟了多線程,系統(tǒng)不得不加鎖领曼,這就嚴(yán)重影響了性能鸥鹉,還可能會(huì)造成死鎖以及產(chǎn)生未知的后果。所以也不要在初始化器中創(chuàng)建線程庶骄。

據(jù)說毁渗,iOS現(xiàn)在可以使用自定義的動(dòng)態(tài)庫(kù),低版本的需要手動(dòng)的使用dlopen()加載单刁。動(dòng)態(tài)庫(kù)上架會(huì)有一些審核的規(guī)則灸异,如不要把x86/i386的包和arm架構(gòu)的包lipo在一起使用。如:

lipo –create Release-iphoneos/libiphone.a Debig-iphonesimulator/libiphone.a –output libiphone.a

如此便將模擬器和設(shè)備的靜態(tài)庫(kù)文件合并成一個(gè)文件輸出了羔飞。

dylib加載調(diào)用

基于上面的分析肺樟,在exec()時(shí),系統(tǒng)內(nèi)核把應(yīng)用映射到新的地址空間逻淌,每次起始位置都是隨機(jī)的么伯。然后使用dyld 加載 dylib 文件(動(dòng)態(tài)鏈接庫(kù)),dyld 在應(yīng)用進(jìn)程中運(yùn)行的工作就是加載應(yīng)用依賴的所有動(dòng)態(tài)鏈接庫(kù)卡儒,準(zhǔn)備好運(yùn)行所需的一切田柔,它擁有和應(yīng)用一樣的權(quán)限。

加載 Dylib時(shí)骨望,先從主執(zhí)行文件的 header 中獲取需要加載的所依賴動(dòng)態(tài)庫(kù)的列表硬爆,從中找到每個(gè) dylib,然后打開文件讀取文件起始位置擎鸠,確保它是 Mach-O 文件(針對(duì)不同運(yùn)行時(shí)可執(zhí)行文件的文件類型)缀磕。然后找到代碼簽名并將其注冊(cè)到內(nèi)核。

應(yīng)用所依賴的 dylib 文件可能會(huì)再依賴其他 dylib糠亩,因此動(dòng)態(tài)庫(kù)列表是一個(gè)遞歸依賴的集合虐骑。一般應(yīng)用會(huì)加載 100 到 400 個(gè) dylib 文件,但大部分都是系統(tǒng) dylib赎线,它們會(huì)被預(yù)先計(jì)算和緩存起來廷没,加載速度很快。但加載內(nèi)嵌(embedded)的 dylib 文件很占時(shí)間垂寥,所以盡可能把多個(gè)內(nèi)嵌 dylib 合并成一個(gè)來加載颠黎,或者使用 static archive另锋。

在加載所有的動(dòng)態(tài)鏈接庫(kù)之后,它們只是處在相互獨(dú)立的狀態(tài)狭归,代碼簽名使得我們不能修改指令夭坪,那樣就不能讓一個(gè) dylib 調(diào)用另一個(gè) dylib。通過fix-up可以將它們結(jié)合起來过椎,dyld 所做的事情就是修正(fix-up)指針和數(shù)據(jù)室梅。Fix-up 有兩種類型,rebasing(在鏡像內(nèi)部調(diào)整指針的指向) 和 binding(將指針指向鏡像外部的內(nèi)容)疚宇。

因?yàn)榈刂房臻g加載隨機(jī)化的緣故亡鼠,二進(jìn)制文件最終的加載地址與預(yù)期地址之間會(huì)存在偏移,所以需要進(jìn)行 rebase 操作敷待,對(duì)那些指向文件內(nèi)部符號(hào)的指針進(jìn)行修正间涵。rebase 完成之后,就會(huì)進(jìn)行 bind 操作榜揖,修正那些指向其他二進(jìn)制文件所包含的符號(hào)的指針勾哩。因?yàn)?dylib 之間有依賴關(guān)系,所以 動(dòng)態(tài)庫(kù)中的好多操作都是沿著依賴鏈遞歸操作的举哟,Rebasing 和 Binding 分別對(duì)應(yīng)著 recursiveRebase() 和 recursiveBind() 這兩個(gè)方法思劳。因?yàn)槭沁f歸,所以會(huì)自底向上地分別調(diào)用 doRebase() 和 doBind() 方法妨猩,這樣被依賴的 dylib 總是先于依賴它的 dylib 執(zhí)行 Rebasing 和 Binding敢艰。

Rebaing 消耗了大量時(shí)間在 I/O 上,在 Rebasing 和 Binding 前會(huì)判斷是否已經(jīng) 預(yù)綁定册赛。如果已經(jīng)進(jìn)行過預(yù)綁定(Prebinding),那就不需要 Rebasing 和 Binding 這些 Fix-up 流程了震嫉,因?yàn)橐呀?jīng)在預(yù)先綁定的地址加載好了森瘪。

Binding 處理那些指向 dylib 外部的指針,它們實(shí)際上被符號(hào)(symbol)名稱綁定票堵,是一個(gè)字符串扼睬。dyld 需要找到 symbol 對(duì)應(yīng)的實(shí)現(xiàn),在符號(hào)表里查找時(shí)需要很多計(jì)算悴势,找到后會(huì)將內(nèi)容存儲(chǔ)起來窗宇。Binding 看起來計(jì)算量比 Rebasing 更大,但其實(shí)需要的 I/O 操作很少特纤,因?yàn)橹?Rebasing 已經(jīng)替 Binding 做過了军俊。Objective-C 中有很多數(shù)據(jù)結(jié)構(gòu)都是靠 Rebasing 和 Binding 來修正(fix-up)的,比如 Class 中指向超類的指針和指向方法的指針捧存。

Objc

C++ 會(huì)為靜態(tài)創(chuàng)建的對(duì)象生成初始化器粪躬,與靜態(tài)語(yǔ)言不同担败,OC基于Runtime機(jī)制可以用類的名字來實(shí)例化一個(gè)類的對(duì)象。Runtime 維護(hù)了一張映射類名與類的全局表镰官,當(dāng)加載一個(gè) dylib 時(shí)提前,其定義的所有的類都需要被注冊(cè)到這個(gè)全局表中。ObjC 在加載時(shí)可以通過 fix-up 在動(dòng)態(tài)類中改變實(shí)例變量的偏移量泳唠,利用這個(gè)技術(shù)可以在不改變dylib的情況下添加另一個(gè) dylib 中類的方法狈网,而非常見的通過定義類別(Category)的方式改變一個(gè)類的方法。

Dyld 在 bind 操作結(jié)束之后笨腥,會(huì)發(fā)出 dyld_image_state_bound 通知拓哺,然后與之綁定的回調(diào)函數(shù) map_2_images 就會(huì)被調(diào)用,它主要做以下幾件事來完成 Objc Setup:

1.讀取二進(jìn)制文件的 DATA 段內(nèi)容扇雕,找到與 objc 相關(guān)的信息拓售。
2.注冊(cè) Objc 類。
3.確保 selector 的唯一性镶奉。
4.讀取 protocol 以及 category 的信息础淤。

除了 map_2_images,我們注意到 _objc_init 還注冊(cè)了 load_images 函數(shù)哨苛,它的作用就是調(diào)用 Objc 的 + load 方法鸽凶,它監(jiān)聽 dyld_image_state_dependents_initialized 通知。

dyld 是運(yùn)行在用戶態(tài)的建峭, 這里由內(nèi)核態(tài)切到了用戶態(tài)玻侥。每當(dāng)有新的鏡像加載之后,都會(huì)執(zhí)行 load-images 方法進(jìn)行回調(diào)亿蒸,這里的回調(diào)是在整個(gè)Objc runtime 初始化時(shí) -objc-init 注冊(cè)的凑兰。有新的鏡像被 map 到 runtime 時(shí),調(diào)用 load-images 方法边锁,并傳入最新鏡像的信息列表 infoList姑食。調(diào)用 prepare-load-methods 對(duì) load 方法的調(diào)用進(jìn)行準(zhǔn)備(將需要調(diào)用 load 方法的類添加到一個(gè)列表中),調(diào)用 -getObjc2NonlazyClassList 獲取所有的類的列表之后茅坛,會(huì)通過 remapClass 獲取類對(duì)應(yīng)的指針音半,然后調(diào)用 schedule-class-load 遞歸地 將當(dāng)前類和沒有調(diào)用 + load 父類進(jìn)入列表。在執(zhí)行 add-class-to-loadable-list(cls) 將當(dāng)前類加入加載列表之前贡蓖,會(huì)先把父類加入待加載的列表曹鸠,保證父類在子類前調(diào)用 load 方法。在執(zhí)行 add-class-to-loadable-list(cls) 將當(dāng)前類加入加載列表之前斥铺,會(huì)先把父類加入待加載的列表彻桃,保證父類在子類前調(diào)用 load 方法。在將鏡像加載到運(yùn)行時(shí)仅父、對(duì) load 方法的準(zhǔn)備就緒叛薯,執(zhí)行 call-load-methods浑吟,開始調(diào)用 load 方法。

由于iOS開發(fā)時(shí)基于Cocoa Touch的耗溜,所以絕大多數(shù)的類起始都是系統(tǒng)類组力,大多數(shù)的Runtime初始化起始在Rebase和Bind中已經(jīng)完成。

Initializers

Objc SetUp 結(jié)束后抖拴,Dyld 便開始運(yùn)行程序的初始化函數(shù)燎字,該任務(wù)由 initializeMainExecutable 函數(shù)執(zhí)行。整個(gè)初始化過程是一個(gè)遞歸的過程阿宅,順序是先將依賴的動(dòng)態(tài)庫(kù)初始化候衍,然后在對(duì)自己初始化。初始化需要做的事情包括:

1.調(diào)用 Objc 類的 + load 函數(shù)洒放。
2.調(diào)用 C++ 中帶有 constructor 標(biāo)記的函數(shù)蛉鹿。
3.非基本類型的 C++ 靜態(tài)全局變量的創(chuàng)建。

所謂執(zhí)行監(jiān)控啟動(dòng)crash的思路都是在這里構(gòu)建的往湿。下面是一些方法的執(zhí)行順序妖异,initialize的順序可能在更早,但總是會(huì)在load和launch之間领追。

程序啟動(dòng)邏輯

主執(zhí)行文件和相關(guān)的 dylib的依賴關(guān)系構(gòu)成了一張巨大的有向圖他膳,執(zhí)行初始化器先加載葉子節(jié)點(diǎn),然后逐步向上加載中間節(jié)點(diǎn)绒窑,直至最后加載根節(jié)點(diǎn)棕孙。這種加載順序確保了安全性,加載某個(gè) dylib 前些膨,其所依賴的其余 dylib 文件肯定已經(jīng)被預(yù)先加載蟀俊。最后 dyld 會(huì)調(diào)用 main() 函數(shù)。main() 會(huì)調(diào)用 UIApplicationMain()订雾,程序啟動(dòng)欧漱。

使用Xcode打開一個(gè)項(xiàng)目,很容易會(huì)發(fā)現(xiàn)一個(gè)文件-main.m文件葬燎,此處就是應(yīng)用的入口了。程序啟動(dòng)時(shí)缚甩,先執(zhí)行main函數(shù)谱净,main函數(shù)是ios程序的入口點(diǎn),內(nèi)部會(huì)調(diào)用UIApplicationMain函數(shù)擅威,UIApplicationMain里會(huì)創(chuàng)建一個(gè)UIApplication對(duì)象 壕探,然后創(chuàng)建UIApplication的delegate對(duì)象 —–(您的)AppDelegate ,開啟一個(gè)消息循環(huán)(main runloop)郊丛,每當(dāng)監(jiān)聽到對(duì)應(yīng)的系統(tǒng)事件時(shí)李请,就會(huì)通知AppDelegate瞧筛。

int main(int argc, char * argv[]) {
  @autoreleasepool {
    return UIApplicationMain(argc, argv, nil,       
    NSStringFromClass([AppDelegate class]));
  }
}

UIApplication對(duì)象是應(yīng)用程序的象征,每一個(gè)應(yīng)用都有自己的UIApplication對(duì)象导盅,而且是單例的较幌。通過[UIApplication sharedApplication]可以獲得這個(gè)單例對(duì)象,一個(gè)iOS程序啟動(dòng)后創(chuàng)建的第一個(gè)對(duì)象就是UIApplication對(duì)象白翻,利用UIApplication對(duì)象乍炉,能進(jìn)行一些應(yīng)用級(jí)別的操作。

UIApplicationMain函數(shù)實(shí)現(xiàn)如下:

int UIApplicationMain{

  int argc,

  char *argv[],

  NSString *principalClassName,

  NSString *delegateClassName

}

第一個(gè)參數(shù)表示參數(shù)的個(gè)數(shù)滤馍,第二個(gè)參數(shù)表示裝載函數(shù)的數(shù)組岛琼,第三個(gè)參數(shù),是UIApplication類名或其子類名巢株,若是nil槐瑞,則默認(rèn)使用UIApplication類名。第四個(gè)參數(shù)是協(xié)議UIApplicationDelegate的實(shí)例化對(duì)象名阁苞,這個(gè)對(duì)象就是UIApplication對(duì)象監(jiān)聽到系統(tǒng)變化的時(shí)候通知其執(zhí)行的相應(yīng)方法困檩。

啟動(dòng)完畢會(huì)調(diào)用 didFinishLaunching方法,并在這個(gè)方法中創(chuàng)建UIWindow猬错,設(shè)置AppDelegate的window屬性窗看,并設(shè)置UIWindow的根控制器。如果有storyboard倦炒,會(huì)根據(jù)info.plist中找到應(yīng)用程序的入口storyboard并加載箭頭所指的控制器显沈,顯示窗口。storyboard和xib最大的不同在于storyboard是基于試圖控制器的逢唤,而非視圖或窗口拉讯。展示之前會(huì)將添加rootViewController的view到UIWindow上面(在這一步才會(huì)創(chuàng)建控制器的view)

[window addSubview: window.rootViewControler.view];

每個(gè)應(yīng)用程序至少有一個(gè)UIWindow,這window負(fù)責(zé)管理和協(xié)調(diào)應(yīng)用程序的屏幕顯示鳖藕,rootViewController的view將會(huì)作為UIWindow的首視圖魔慷。

程序啟動(dòng)的完整過程如下:

1.main 函數(shù)。
2.UIApplicationMain
a.創(chuàng)建UIApplication對(duì)象著恩。
b.創(chuàng)建UIApplication的delegate對(duì)象院尔。

  1. delegate對(duì)象開始處理(監(jiān)聽)系統(tǒng)事件(沒有storyboard)
    a.程序啟動(dòng)完畢的時(shí)候, 就會(huì)調(diào)用代理的application:didFinishLaunchingWithOptions:方法。
    b.在application:didFinishLaunchingWithOptions:中創(chuàng)建UIWindow喉誊。
    c.創(chuàng)建和設(shè)置UIWindow的rootViewController邀摆。
    d.顯示窗口。
    4.根據(jù)Info.plist獲得最主要storyboard的文件名,加載最主要storyboard(有storyboard)伍茄。
    a.創(chuàng)建UIWindow栋盹。
    b.創(chuàng)建和設(shè)置UIWindow的rootViewController。
    c.顯示窗口敷矫。

AppDelegate的代理方法

//app啟動(dòng)完畢后就會(huì)調(diào)用
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
}

//app程序失去焦點(diǎn)就會(huì)調(diào)用                    
- (void)applicationWillResignActive:(UIApplication *)application
{
}

//app進(jìn)入后臺(tái)的時(shí)候調(diào)用例获, 一般在這里保存應(yīng)用的數(shù)據(jù)(游戲數(shù)據(jù),比如暫停游戲)
- (void)applicationDidEnterBackground:(UIApplication *)application
{
}

//app程序程序從后臺(tái)回到前臺(tái)就會(huì)調(diào)用
- (void)applicationWillEnterForeground:(UIApplication *)application
{
}

//app程序獲取焦點(diǎn)就會(huì)調(diào)用
- (void)applicationDidBecomeActive:(UIApplication *)application
{
 }

// 內(nèi)存警告汉额,可能要終止程序,清除不需要再使用的內(nèi)存
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
{
}

// 程序即將退出調(diào)用
- (void)applicationWillTerminate:(UIApplication *)application
{
}

AppDelegate加載順序
1.application:didFinishLaunchingWithOptions:
2.applicationDidBecomeActive:

ViewController中的加載順序
1.loadView
2.viewDidLoad
3.viewWillAppear
4.viewWillLayoutSubviews
5.viewDidLayoutSubviews
6.viewDidAppear

View中的加載順序
1.initWithCoder(如果沒有storyboard就會(huì)調(diào)用initWithFrame榨汤,這里兩種方法視為一種)
2.awakeFromNib
3.layoutSubviews
4.drawRect

一些方法的使用時(shí)機(jī)

+ (void)load;

應(yīng)用程序啟動(dòng)就會(huì)調(diào)用的方法蠕搜,在這個(gè)方法里寫的代碼最先調(diào)用。

+ (void)initialize;

用到本類時(shí)才調(diào)用件余,這個(gè)方法里一般設(shè)置導(dǎo)航控制器的主題等讥脐,如果在后面的方法設(shè)置導(dǎo)航欄主題就太遲了!

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions;

這個(gè)方法里面會(huì)創(chuàng)建UIWindow啼器,設(shè)置根控制器并展現(xiàn)旬渠,比如某些應(yīng)用程序要加載授權(quán)頁(yè)面也是在這加,也可以設(shè)置觀察者端壳,監(jiān)聽到通知切換根控制器等告丢。

- (void)awakeFromNib;

在使用IB的時(shí)候才會(huì)涉及到此方法的使用损谦,當(dāng).nib文件被加載的時(shí)候照捡,會(huì)發(fā)送一個(gè)awakeFromNib的消息到.nib文件中的每個(gè)對(duì)象栗精,每個(gè)對(duì)象都可以定義自己的awakeFromNib函數(shù)來響應(yīng)這個(gè)消息悲立,執(zhí)行一些必要的操作脚草。在這個(gè)方法里設(shè)置view的背景等一系列普通操作馏慨。

- (void)loadView;

創(chuàng)建視圖的層次結(jié)構(gòu),在沒有創(chuàng)建控制器的view的情況下不能直接寫 self.view 因?yàn)閟elf.view的底層是:

if(_view == nil){
_view = [self loadView]
}

這么寫會(huì)直接造成死循環(huán)。

如果重寫這個(gè)loadView方法里面什么都不寫叮盘,會(huì)顯示黑屏。

- (void)viewWillLayoutSubviews;

視圖將要布局子視圖毒费,蘋果建議的設(shè)置界面布局屬性的方法觅玻,這個(gè)方法和viewWillAppear里溪厘,系統(tǒng)的底層都是沒有寫任何代碼的畸悬,也就是說這里面不寫super 也是可以的蹋宦。

 - (void)layoutSubviews;

在這個(gè)方法里一般設(shè)置子控件的frame。

- (void)drawRect:(CGRect)rect;

UI控件都是畫上去的蒿辙,在這一步就是把所有的東西畫上去。drawRect方法只能在加載時(shí)調(diào)用一次习瑰,如果后面還需要調(diào)用甜奄,比如下載進(jìn)度的圓弧窃款,需要一直刷幀课兄,就要使用setNeedsDisplay來定時(shí)多次調(diào)用本方法晨继。

- (void)applicationDidBecomeActive:(UIApplication *)application;

這是AppDelegate的應(yīng)用程序獲取焦點(diǎn)方法,真正到了這里,才是所有東西全部加載完畢唉擂。

啟動(dòng)分析

應(yīng)用啟動(dòng)時(shí),會(huì)播放一個(gè)啟動(dòng)動(dòng)畫檀葛。iPhone上是400ms玩祟,iPad上是500ms屿聋。如果應(yīng)用啟動(dòng)過慢润讥,用戶就會(huì)放棄使用黑忱,甚至永遠(yuǎn)都不再回來勒魔。為了防止一個(gè)應(yīng)用占用過多的系統(tǒng)資源抚吠,開發(fā)iOS的蘋果工程師門設(shè)計(jì)了一個(gè)“看門狗”的機(jī)制。在不同的場(chǎng)景下弟胀,“看門狗”會(huì)監(jiān)測(cè)應(yīng)用的性能楷力。如果超出了該場(chǎng)景所規(guī)定的運(yùn)行間,“看門狗”就會(huì)強(qiáng)制終結(jié)這個(gè)應(yīng)用的進(jìn)程孵户。

iOS App啟動(dòng)時(shí)會(huì)鏈接并加載Framework和Static lib萧朝,執(zhí)行UIKit初始化,然后進(jìn)入應(yīng)用程序回調(diào)夏哭,執(zhí)行Core Animation transaction等检柬。每個(gè)Framework都會(huì)增加啟動(dòng)時(shí)間和占用的內(nèi)存,不要鏈接不必要的Framework竖配,必要的Framework不要標(biāo)記為Optional何址。避免創(chuàng)建全局的C++對(duì)象。

初始化UIKit時(shí)字體进胯、狀態(tài)欄用爪、user defaults、Main.storyboard會(huì)被初始化胁镐。User defaults本質(zhì)上是一個(gè)plist文件偎血,保存的數(shù)據(jù)是同時(shí)被反序列化的诸衔,不要在user defaults里面保存圖片等大數(shù)據(jù)。

對(duì)于 OC 來說應(yīng)盡量減少 class,selector 和 category 這些元數(shù)據(jù)的數(shù)量颇玷。編碼原則和設(shè)計(jì)模式之類的理論會(huì)鼓勵(lì)大家多寫精致短小的類和方法署隘,并將每部分方法獨(dú)立出一個(gè)類別,但這會(huì)增加啟動(dòng)時(shí)間亚隙。在調(diào)用的地方使用初始化器,不要使用atribute((constructor)) 將方法顯式標(biāo)記為初始化器违崇,而是讓初始化方法調(diào)用時(shí)才執(zhí)行阿弃。比如使用 dispatch_once(),pthread_once() 或 std::once()。也就是在第一次使用時(shí)才初始化羞延,推遲了一部分工作耗時(shí)渣淳。

建立網(wǎng)絡(luò)連接前需要做域名解析,如果網(wǎng)關(guān)出現(xiàn)問題伴箩,dns解析不正常時(shí)入愧,dns的超時(shí)時(shí)間是應(yīng)用控制不了的。在程序設(shè)計(jì)時(shí)要考慮這些問題嗤谚,如果程序啟動(dòng)時(shí)有網(wǎng)絡(luò)連接棺蛛,應(yīng)盡快的結(jié)束啟動(dòng)過程,網(wǎng)絡(luò)訪問通過線程解決巩步,而不阻塞主線程的運(yùn)行旁赊。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市椅野,隨后出現(xiàn)的幾起案子终畅,更是在濱河造成了極大的恐慌,老刑警劉巖竟闪,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件离福,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡炼蛤,警方通過查閱死者的電腦和手機(jī)妖爷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鲸湃,“玉大人赠涮,你說我怎么就攤上這事“堤簦” “怎么了笋除?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)炸裆。 經(jīng)常有香客問我垃它,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任国拇,我火速辦了婚禮洛史,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘酱吝。我一直安慰自己也殖,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布务热。 她就那樣靜靜地躺著忆嗜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪崎岂。 梳的紋絲不亂的頭發(fā)上捆毫,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音冲甘,去河邊找鬼绩卤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛江醇,可吹牛的內(nèi)容都是我干的濒憋。 我是一名探鬼主播,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼嫁审,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼跋炕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起律适,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤辐烂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后捂贿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纠修,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年厂僧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扣草。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡颜屠,死狀恐怖辰妙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情甫窟,我是刑警寧澤密浑,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站粗井,受9級(jí)特大地震影響尔破,放射性物質(zhì)發(fā)生泄漏街图。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一懒构、第九天 我趴在偏房一處隱蔽的房頂上張望餐济。 院中可真熱鬧,春花似錦胆剧、人聲如沸絮姆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)滚朵。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間太雨,已是汗流浹背纫雁。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留递惋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像椿疗,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子糠悼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

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