iOS-底層探索30:?jiǎn)?dòng)優(yōu)化(Clang插樁)

iOS 底層探索 文章匯總

目錄


一温治、查看APP啟動(dòng)耗時(shí)

main函數(shù)之前的處理為pre-mian階段饭庞,這篇文章主要分析這個(gè)階段。
添加DYLD_PRINT_STATISTICS參數(shù)打印出pre-mian階段的耗時(shí)情況:

各時(shí)段處理耗時(shí)分析:
  1. Total pre-main time: 總耗時(shí)
  2. dylib loading time: 動(dòng)態(tài)庫(kù)載入耗時(shí)
  3. rebase/binding time: rebase表示地址偏移修正(ASLR)熬荆,binding表示符號(hào)綁定
  4. ObjC setup time: OC類注冊(cè)耗時(shí)
  5. initializer time: 執(zhí)行load構(gòu)造函數(shù)的耗時(shí)
    slowest intializers :
  6. libSystem.B.dylib : 系統(tǒng)的
  7. libMainThreadChecker.dylib :
  8. XXXXX : 項(xiàng)目主程序耗時(shí)
pre-main優(yōu)化方向:
  1. 官方建議非系統(tǒng)動(dòng)態(tài)庫(kù)的加載個(gè)數(shù)不超過6個(gè)舟山,多于6個(gè)就要考慮動(dòng)態(tài)庫(kù)的合并;
  2. 減少OC類,減少C++虛函數(shù)
  3. 減少load方法和構(gòu)造函數(shù)
main方法之后優(yōu)化方向:
  1. 延遲初始化卤恳、懶加載
  2. 刪除不使用類捏顺、方法、圖片資源
  3. 盡量不用XIBStoryboard纬黎,特別是首屏界面
    參考:
    iOS 腳本查看項(xiàng)目中未使用的類幅骄、iOS 腳本查看項(xiàng)目未使用到的方法iOS 腳本查找項(xiàng)目中無(wú)用資源腳本原理

二本今、虛擬內(nèi)存和物理內(nèi)存

1拆座、虛擬內(nèi)存和物理內(nèi)存的區(qū)別

當(dāng)我們向系統(tǒng)申請(qǐng)內(nèi)存時(shí),系統(tǒng)并不會(huì)給你返回物理內(nèi)存的地址冠息,而是給你一個(gè)虛擬內(nèi)存地址挪凑。CPU讀取數(shù)據(jù)時(shí)也是通過內(nèi)存管理單元MMU虛擬地址映射到物理內(nèi)存地址。每個(gè)進(jìn)程都擁有相同大小的虛擬地址空間逛艰,對(duì)于32位的進(jìn)程躏碳,可以擁有4GB的虛擬內(nèi)存,64位進(jìn)程則更多散怖,可達(dá)18EB菇绵。只有我們開始使用申請(qǐng)到的虛擬內(nèi)存時(shí),系統(tǒng)才會(huì)將虛擬地址映射到物理地址上镇眷,從而讓程序使用真實(shí)的物理內(nèi)存咬最。

2、內(nèi)存分頁(yè)

系統(tǒng)會(huì)對(duì)虛擬內(nèi)存和物理內(nèi)存進(jìn)行分頁(yè)欠动,虛擬內(nèi)存到物理內(nèi)存的映射都是以頁(yè)為最小粒度的永乌。在OSX和早期的iOS系統(tǒng)中,物理和虛擬內(nèi)存都按照4KB的大小進(jìn)行分頁(yè)具伍。iOS近期的系統(tǒng)中翅雏,基于A7A8處理器的系統(tǒng),物理內(nèi)存按照4KB分頁(yè)人芽,虛擬內(nèi)存按照16KB分頁(yè)望几。基于A9處理器的系統(tǒng)啼肩,物理和虛擬內(nèi)存都是以16KB進(jìn)行分頁(yè)橄妆。(終端輸入PAGESIZE可以查看到macOS的分頁(yè)大小)衙伶。

系統(tǒng)將內(nèi)存頁(yè)分為三種狀態(tài)祈坠。
  • 活躍內(nèi)存頁(yè)(active pages)- 這種內(nèi)存頁(yè)已經(jīng)被映射到物理內(nèi)存中害碾,而且近期被訪問過,處于活躍狀態(tài)赦拘。
  • 非活躍內(nèi)存頁(yè)(inactive pages)- 這種內(nèi)存頁(yè)已經(jīng)被映射到物理內(nèi)存中慌随,但是近期沒有被訪問過。
  • 可用的內(nèi)存頁(yè)(free pages)- 沒有關(guān)聯(lián)到虛擬內(nèi)存頁(yè)的物理內(nèi)存頁(yè)集合躺同。

當(dāng)可用的內(nèi)存頁(yè)降低到一定的閥值時(shí)阁猜,系統(tǒng)就會(huì)采取低內(nèi)存應(yīng)對(duì)措施,在OSX中蹋艺,系統(tǒng)會(huì)將非活躍內(nèi)存頁(yè)交換到硬盤上剃袍,而在iOS中,則會(huì)觸發(fā)Memory Warning捎谨,如果你的App沒有處理低內(nèi)存警告并且還在后臺(tái)占用太多內(nèi)存民效,則有可能被殺掉。

3涛救、如何解決內(nèi)存浪費(fèi)的畏邢?

應(yīng)用程序加載到內(nèi)存中時(shí),并不會(huì)全部加載到物理內(nèi)存中检吆,屬于懶加載舒萎,用哪一部分就加載那一部分。當(dāng)訪問進(jìn)程的內(nèi)存地址時(shí)蹭沛,首先看頁(yè)表臂寝,查看所要訪問的對(duì)應(yīng)頁(yè)表是否已經(jīng)加載到內(nèi)存中。如果這一頁(yè)沒有在物理內(nèi)存中時(shí)摊灭,操作系統(tǒng)會(huì)阻塞當(dāng)前進(jìn)程交煞,發(fā)出一個(gè)缺頁(yè)異常/缺頁(yè)中斷(pagefault),讓后將磁盤中對(duì)應(yīng)頁(yè)的數(shù)據(jù)加載到內(nèi)存中斟或,完成虛擬內(nèi)存和物理內(nèi)存的映射素征。
當(dāng)前進(jìn)程的頁(yè)表數(shù)據(jù)加載到物理內(nèi)存中時(shí),不一定是連續(xù)的萝挤,也有可能會(huì)覆蓋其他進(jìn)程的不活躍頁(yè)御毅,這樣的按需分配,極大提高內(nèi)存的使用效率怜珍。

4端蛆、虛擬內(nèi)存的安全問題

虛擬內(nèi)存通過頁(yè)表映射到物理內(nèi)存上,因此直接訪問物理地址并不能實(shí)際正確的拿到進(jìn)程的數(shù)據(jù)酥泛,但是進(jìn)程的虛擬內(nèi)存地址相對(duì)于自己來(lái)說也是絕對(duì)的今豆,不管程序運(yùn)行多少次嫌拣,如果訪問同一個(gè)函數(shù),它在虛擬內(nèi)存中的地址都是一樣的這樣也存在安全問題(比如直接靜態(tài)注入)呆躲。
這樣也出現(xiàn)了新的技術(shù)--ASLR(Address Space Layout Randomization)异逐。
每次虛擬內(nèi)存在加載之前,都加一個(gè)隨機(jī)偏移值插掂。

三灰瞻、二進(jìn)制重排原理

1、什么是二進(jìn)制重排

缺頁(yè)中斷/缺頁(yè)異常:內(nèi)存分頁(yè)管理辅甥,每一頁(yè)加載的時(shí)候都會(huì)發(fā)生酝润。
在iOS中,在加載缺頁(yè)內(nèi)存的時(shí)候璃弄,不僅發(fā)生缺頁(yè)阻塞從磁盤中加載數(shù)據(jù)要销,還要對(duì)加載的這頁(yè)做簽名驗(yàn)證
App使用中不會(huì)發(fā)生大量的pagefault夏块,我們一般感受不到這個(gè)過程疏咐。但是在啟動(dòng)時(shí),程序有大量的代碼需要加載拨扶、執(zhí)行凳鬓,那么這個(gè)缺頁(yè)中斷有可能就很明顯了。

如何優(yōu)化患民?

假如我的App只有10頁(yè)數(shù)據(jù)缩举,但是啟動(dòng)的時(shí)候需要加載的代碼分散放在1、3匹颤、5頁(yè)仅孩。因?yàn)榇a在Mach-o文件中的位置是根據(jù)文件加載生成的順序來(lái)決定。那么這時(shí)候App啟動(dòng)需要運(yùn)行的代碼放在3個(gè)虛擬內(nèi)存頁(yè)中就會(huì)出現(xiàn)3pagefault印蓖。
如果我們將需要啟動(dòng)用的代碼全部放在第1頁(yè)中辽慕,那么App啟動(dòng)時(shí)便只會(huì)觸發(fā)一次pagefaultApp啟動(dòng)加載的數(shù)據(jù)也會(huì)變少赦肃,這樣極大減少進(jìn)程的阻塞溅蛉。這就是二進(jìn)制重排的原理。

2他宛、查看pagefault

Xcode提供相關(guān)的調(diào)試工具船侧,打開Instruments-System Trace,選中手機(jī)中的App厅各,點(diǎn)擊System Trace左上角開始記錄后會(huì)自動(dòng)打開手機(jī)中的App镜撩,進(jìn)入首屏后點(diǎn)擊System Trace左上角停止。查看Main Thread中虛擬內(nèi)存的File Backed Page In項(xiàng)目队塘,它代表著啟動(dòng)時(shí)產(chǎn)生的pagefault次數(shù)袁梗。

查看pagefault次數(shù)時(shí)受App冷啟動(dòng)熱啟動(dòng)影響很大宜鸯,可以先開啟幾個(gè)其他App然后等一段時(shí)間再點(diǎn)擊System Trace左上角開啟記錄。

二進(jìn)制重排的優(yōu)化是發(fā)生在編譯鏈接階段遮怜,對(duì)即將生成的二進(jìn)制可執(zhí)行文件進(jìn)行重排淋袖。
Xcode使用的連接器叫ld它可以指向一個(gè)order_file文件,在這個(gè)文件中指定排列符號(hào)奈泪,那么Xcode在編譯時(shí)會(huì)按照指定的排列編譯出可執(zhí)行的文件适贸,蘋果objc源碼項(xiàng)目中的libobjc.order文件就是實(shí)現(xiàn)二進(jìn)制重排功能的灸芳。

四涝桅、實(shí)現(xiàn)二進(jìn)制重排

1、查看方法排列順序

新建測(cè)試項(xiàng)目Test_TracingPCs
在項(xiàng)目的build settings中搜索link map開啟這個(gè)文件的輸出

重新編譯后就可以在工程的build目錄里面找到一份link map文件
路徑如下:
Xcode -> DerivedData-> 項(xiàng)目名-> Build-> Intermediates.noindex-> 項(xiàng)目名.build-> Debug-iphoneos-> 項(xiàng)目名.build-> 項(xiàng)目名-LinkMap-normal-arm64.txt

這個(gè)文件里面就記錄一些鏈接.o的文件烙样、Mach-o文件里的一些信息冯遂、符號(hào)信息symbols等等…

注意,這個(gè)symbols就是關(guān)注的要點(diǎn):默認(rèn)情況下它是按照Build Phases-Compile Sources中編譯文件從上至下排序以及類中方法從上至下排序谒获。

2蛤肌、通過order文件重新排列加載順序:

在項(xiàng)目根目錄創(chuàng)建lcj.order文件,在工程配置中添加.order文件的路徑./lcj.order后批狱,讓編譯器按照指定的順序重新排列二進(jìn)制文件裸准,把最需要加載的代碼段放在內(nèi)存頁(yè)靠前的位置。

這里只是演示了讓viewcontroller中的幾個(gè)自定義方法優(yōu)先靠排列在內(nèi)存分頁(yè)中,實(shí)際中一個(gè)App啟動(dòng)時(shí)的page fault可能多達(dá)幾千次赔硫,那么需要重排的函數(shù)遠(yuǎn)不止這一點(diǎn)炒俱。

五、Clang插樁

1爪膊、引入Clang插樁

由于項(xiàng)目中存在大量的函數(shù)方法調(diào)用权悟,此外還有Block、Swift推盛、C峦阁、C++函數(shù),因此僅僅HOOK msgSend方法不可行耘成。因?yàn)?code>Clang會(huì)讀取所有代碼榔昔,分析AST中所有節(jié)點(diǎn),所有通過Clang插樁可以實(shí)現(xiàn)100%的符號(hào)覆蓋瘪菌。

抖音研發(fā)實(shí)踐:基于二進(jìn)制文件重排的解決方案

官方插樁工具-Tracing PCs
Clang Documentation
Tracing PCs

2撒会、使用Tracing PCs

根據(jù)官方文檔添加-fsanitize-coverage=trace-pc-guard標(biāo)記

ViewController.m中添加兩個(gè)官方文檔中的方法實(shí)現(xiàn):

void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
                                                    uint32_t *stop) {
  static uint64_t N;  // Counter for the guards.
  if (start == stop || *start) return;  // Initialize only once.
  printf("INIT: %p %p\n", start, stop);
  for (uint32_t *x = start; x < stop; x++)
    *x = ++N;  // Guards should start from 1.
}

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
  if (!*guard) return;
  void *PC = __builtin_return_address(0);
  char PcDescr[1024];
    
  //__sanitizer_symbolize_pc(PC, "%p %F %L", PcDescr, sizeof(PcDescr));
  printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}

-[ViewController viewDidLoad]前添加斷點(diǎn),運(yùn)行項(xiàng)目控嗜,到斷點(diǎn)后打開匯編斷點(diǎn)(菜單欄Debug->Debug Workflow->Always Show Disassembly)

結(jié)合匯編中插入的__sanitizer_cov_trace_pc_guard代碼和控制臺(tái)打印的信息分析可知:添加-fsanitize-coverage=trace-pc-guard標(biāo)記后Clang會(huì)在中間代碼IR中的每個(gè)方法茧彤、Block等調(diào)用邊緣插入__sanitizer_cov_trace_pc_guard方法的調(diào)用。
所以Clang插樁插入的就是__sanitizer_cov_trace_pc_guard方法調(diào)用疆栏。

3曾掂、修改__sanitizer_cov_trace_pc_guard方法惫谤,獲取函數(shù)的調(diào)用方法名
#import <dlfcn.h>
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
    //排除load方法
    //if (!*guard) return;
    
    //當(dāng)前函數(shù)返回到上一個(gè)方法繼續(xù)執(zhí)行的地址
    void *PC = __builtin_return_address(0);
    Dl_info info;
    dladdr(PC, &info);
    printf("fname:%s \nfbase:%p \nsname:%s \nsaddr:%p\n\n\n",info.dli_fname,info.dli_fbase,info.dli_sname,info.dli_saddr);
    
    char PcDescr[1024];
    //__sanitizer_symbolize_pc(PC, "%p %F %L", PcDescr, sizeof(PcDescr));
    printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
}

點(diǎn)擊屏幕輸出:

fname:/private/var/containers/Bundle/Application/BAE470B2.../Test_TracingPCs.app/Test_TracingPCs 
fbase:0x10236c000 
sname:-[ViewController touchesBegan:withEvent:] 
saddr:0x102371ad4
4、將獲取到的符號(hào)寫入到. order文件中
#import <libkern/OSAtomic.h>//用于定義原子隊(duì)列

//定義原子隊(duì)列
static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;

//定義符號(hào)結(jié)構(gòu)體
typedef struct{
    void *pc;
    void *next;
} SYNode;

+ (void)load {
    
}
- (void)viewDidLoad {
    [super viewDidLoad];
    
    testCFunc();
    [self testOCFunc];
}
- (void)testOCFunc {
    NSLog(@"OC函數(shù)");
}
void testCFunc() {
    CJBlock();
}
void(^CJBlock)(void) = ^(void) {
    NSLog(@"Block");
};

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
    //排除load方法
    //if (!*guard) return;
    
    //當(dāng)前函數(shù)返回到上一個(gè)方法繼續(xù)執(zhí)行的地址
    void *PC = __builtin_return_address(0);
    //創(chuàng)建結(jié)構(gòu)體!
    SYNode * node = malloc(sizeof(SYNode));
    *node = (SYNode){PC,NULL};
    
    //該方法在子線程中調(diào)用珠洗,因此需要使用線程安全的Atomic原子隊(duì)列
    //加入結(jié)構(gòu)
    OSAtomicEnqueue(&symbolList, node, offsetof(SYNode, next));
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //定義數(shù)組
    NSMutableArray<NSString *> *symbolNames = [NSMutableArray array];
    
    while (YES) {//一次循環(huán)!也會(huì)被HOOK一次!!(Tracing PCs只要有跳轉(zhuǎn)(匯編中b/bl指令)就會(huì)被HOOK)
        SYNode *node = OSAtomicDequeue(&symbolList, offsetof(SYNode, next));
        
        if (node == NULL) {
            break;
        }
        Dl_info info = {0};
        dladdr(node->pc, &info);
        printf("%s \n",info.dli_sname);
        NSString *name = @(info.dli_sname);
        free(node);
        
        //C函數(shù)前需加 _
        BOOL isObjc = [name hasPrefix:@"+["]||[name hasPrefix:@"-["];
        NSString *symbolName = isObjc ? name : [@"_" stringByAppendingString:name];
        
        if (![symbolNames containsObject:symbolName]) {
            [symbolNames addObject:symbolName];
        }
    }
    //反向數(shù)組
    symbolNames = (NSMutableArray<NSString *>*)[[symbolNames reverseObjectEnumerator] allObjects];
    //去掉當(dāng)前方法
    [symbolNames removeObject:[NSString stringWithFormat:@"%s",__FUNCTION__]];
    
    //數(shù)組轉(zhuǎn)成字符串
    NSString *funcStr = [symbolNames componentsJoinedByString:@"\n"];
    //字符串寫入文件
    NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"lcj.order"];
    //文件內(nèi)容
    NSData *fileContents = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
    [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
}

由于Tracing PCs只要有跳轉(zhuǎn)(匯編中b/bl指令)就會(huì)被HOOK溜歪,因此while也會(huì)被HOOK,為了避免循環(huán)調(diào)用需要修改Other C Flags為:-fsanitize-coverage=func,trace-pc-guard

運(yùn)行后點(diǎn)擊屏幕拿到.order文件

六许蓖、其他問題

1蝴猪、Swift 工程 / 混編工程問題

通過上面的方法可以拿到OC項(xiàng)目中的符號(hào),想要拿到Swift中的符號(hào)還需要做以下配置:
-sanitize-coverage=func
-sanitize=undefined

2膊爪、cocoapod 工程問題

對(duì)于cocoapod工程引入的庫(kù) , 由于針對(duì)不同的target自阱。那么我們?cè)谥鞒绦蛑械?code>target添加的編譯設(shè)置Write Link Map File , -fsanitize-coverage=func,trace-pc-guard以及order file等設(shè)置肯定是不會(huì)生效的。解決方法就是針對(duì)需要的target去做對(duì)應(yīng)的設(shè)置即可(配置target自己的Order File)米酬。

對(duì)于直接手動(dòng)導(dǎo)入到工程里的SDK , 不管是靜態(tài)庫(kù).a還是動(dòng)態(tài)庫(kù)沛豌, 默認(rèn)在主工程設(shè)置就可以可以拿到符號(hào)的。

手動(dòng)導(dǎo)入的三方庫(kù)如果沒有導(dǎo)入使用的話 , 是不會(huì)加載的赃额,添加了load方法也是如此加派。


參考

iOS 優(yōu)化篇 - 啟動(dòng)優(yōu)化之Clang插樁實(shí)現(xiàn)二進(jìn)制重排

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市跳芳,隨后出現(xiàn)的幾起案子芍锦,更是在濱河造成了極大的恐慌,老刑警劉巖飞盆,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件娄琉,死亡現(xiàn)場(chǎng)離奇詭異粹排,居然都是意外死亡俺孙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門十酣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)照瘾,“玉大人匈棘,你說我怎么就攤上這事∥雒” “怎么了主卫?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)鹃愤。 經(jīng)常有香客問我簇搅,道長(zhǎng),這世上最難降的妖魔是什么软吐? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任瘩将,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘姿现。我一直安慰自己肠仪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布备典。 她就那樣靜靜地躺著异旧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪提佣。 梳的紋絲不亂的頭發(fā)上吮蛹,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音拌屏,去河邊找鬼潮针。 笑死,一個(gè)胖子當(dāng)著我的面吹牛槐壳,可吹牛的內(nèi)容都是我干的然低。 我是一名探鬼主播喜每,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼务唐,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了带兜?” 一聲冷哼從身側(cè)響起枫笛,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎刚照,沒想到半個(gè)月后刑巧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡无畔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年啊楚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浑彰。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡恭理,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出郭变,到底是詐尸還是另有隱情颜价,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布诉濒,位于F島的核電站周伦,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏未荒。R本人自食惡果不足惜专挪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧寨腔,春花似錦困肩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至靖避,卻和暖如春潭枣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背幻捏。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工盆犁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人篡九。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓谐岁,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親榛臼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子伊佃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

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