iOS Crash 流程化4:打造自己的收集漾稀、符號化程序

Table of Contents

  • iOS Crash 流程化4:打造自己的收集、符號化程序
    • 實現(xiàn)代碼
    • 發(fā)布包沒帶符號表
    • Mach-O File Format
      • header
      • load Command
        • LC_SEGMENT
        • LC_SYMTAB
      • 數(shù)據(jù)部分
      • 小小結
    • 獲取構架建瘫、鏡像加載地址
    • 輸出Crash日志
    • 小結

當APP發(fā)布到AppStore后崭捍,如果發(fā)生了Crash,通常情況下我們拿不到崩潰手機啰脚,也就是說拿不到Crash日志殷蛇。這是一個棘手的問題。有人說可以在開發(fā)者中心找到用戶上傳到蘋果的日志橄浓,但是粒梦,不是所有的用戶都會在程序Crash后上傳Crash日志,所以有必要打造一個屬于我們自己的異常收集系統(tǒng)荸实。

下面就講講打造的異常收集系統(tǒng)谍倦,主要思路:使用NSSetUncaughtExceptionHandler注冊異常處理函數(shù),當APP 發(fā)生Crash時泪勒,回調到異常處理函數(shù)昼蛀,在異常處理函數(shù)中收集Crash信息,然后上傳到服務器圆存;當需要分析的時候叼旋,從服務器取回Crash日志,如果沒有符號化沦辙,使用atos命令符號化夫植。由于暫時沒有服務器,就保存到了沙盒路徑的Document目錄下油讯,可以使用itunes方便的導出日志详民。這里提供了一個簡單示例代碼:UncaughtException嘀略,先從代碼入手洼哎。

實現(xiàn)代碼

這里會分別列出關鍵的代碼埋合。下面是 AppDelegate.m 中的代碼

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [LJCaughtException setDefaultHandler];
    // Override point for customization after application launch.
    return YES;
}

application:didFinishLaunchingWithOptions:中注冊異常處理函數(shù)剃诅,所有的異常注冊和異常處理函數(shù)的代碼都封裝到LJCaughtException.m中饥瓷,如下:

///先前注冊的處理句柄
NSUncaughtExceptionHandler *preHander;

/// 異常處理函數(shù)
void UncaughtExceptionHandler(NSException * exception)
{
    [LJCaughtException  processException:exception];
}

@implementation LJCaughtException

+ (void)setDefaultHandler
{
        ///首先保存先前注冊的異常處理句柄
    preHander = [LJCaughtException getHandler];
    ///注冊異常處理句柄
    NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
}

+ (NSUncaughtExceptionHandler *)getHandler
{
    return NSGetUncaughtExceptionHandler();
}

///異常處理句柄
+ (void)processException:(NSException *)exception
{
    /// 異常的堆棧信息
    NSArray *aryCrashBackTrace = [exception callStackSymbols];
    if (!aryCrashBackTrace)
    {
        return;
    }
    /// 出現(xiàn)異常的原因
    NSString *strCrashReason = [exception reason];

    /// 異常名稱
    NSString *strCrashName = [exception name];

    ....
}
... 

@end

上面代碼可以分解為三個部分理解:

  1. 定義異常處理函數(shù)颂碧,異常處理函數(shù)的原型為:
typedef void NSUncaughtExceptionHandler(NSException *exception);
  1. 注冊異常處理函數(shù):使用NSSetUncaughtExceptionHandler注冊異常處理函數(shù),注冊的代碼為:NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler)

  2. 執(zhí)行異常處理函數(shù):當異常發(fā)生時窟却,自動執(zhí)行異常處理函數(shù)剖笙。異常處理函數(shù)內部完成收集Crash信息的功能。

下面是在Debug和Release模式下涧窒,Crash時捕獲的線程回溯:


收集心肪、解析IOS崩潰日式10

可以看出,使用系統(tǒng)的API可以完美的捕獲到崩潰日志纠吴,而且符號化了硬鞍,一行代碼 callStackSymbols 就獲取了異常線程的回溯并完成了符號化工作。其實戴已,事情沒有這么簡單固该,不妨試試發(fā)布包,是不是也能像在debug和release模式那樣恭陡,獲取到符號化的異常線程回溯蹬音?

發(fā)布包沒帶符號表

將測試程序打為發(fā)布包,查看異常線程回溯圖休玩,如下:

發(fā)布包的Crash日志


收集著淆、解析IOS崩潰日式11

圖中紅框是異常線程的關鍵回溯,顯示的是鏡像的名字拴疤,沒有被轉化為有效的代碼符號永部。為什么?

仔細想想呐矾,前面提到符號化的前提條件苔埋,是得有符號表,那么我們推測debug和release的APP包含了符號表蜒犯,而發(fā)布包沒有包含符號表组橄,是不是?在終端中使用nm命令驗證下罚随。

收集玉工、解析IOS崩潰日式13

確實是,發(fā)布包沒有符號表淘菩,為什么遵班?

原來,符號表是一個debug產(chǎn)物潮改,如果使用archive模式打包狭郑,那么符號表會被剪裁掉。不過你也可以在Xcode的編譯選項中配置為符號表不剪裁汇在。方法是設置Strip Style選項為Debugging Symbols翰萨。下圖是設置發(fā)布包帶符號表的方法:

image

但是這會讓最后生成的IPA變大不少(5%)。用我們項目測試趾疚,居然大了約30%缨历,可能是代碼太多的原因吧以蕴。這個對于嚴格限制APP大小的人來說糙麦,是無法接受的辛孵。

天無絕人之路,在使用archive打包時赡磅,生成了一個dSYM符號文件魄缚,這個文件不發(fā)布,在本地保存著焚廊。這個文件太有用了冶匹,也是我們符號化的唯一選擇了。

顯然咆瘟,對于發(fā)布到用戶手中的發(fā)布包嚼隘,在程序Crash后,不能在用戶設備上完成符號化工作袒餐,callStackSymbols只能返回帶地址的日志信息飞蛹,需要我們線下符號化,還好蘋果提供了一個命令行工具—–atos灸眼,完成符號化工作卧檐。

若想通過atos工具在符號文件中查找到地址對應的符號,需要代碼構架焰宣、鏡像加載地址這兩個參數(shù)霉囚,查看發(fā)布包的Crash日志圖片,這兩個參數(shù)都沒有匕积,怎么辦盈罐?只能祭出OS X ABI Mach-O File Format ReferenceKSCrash 開源框架這兩個終極神器。

OS X ABI Mach-O File Format Reference闡述了可執(zhí)行二進制程序的存儲格式闪唆,提供原理性的支撐盅粪。

KSCrash包含了獲取代碼構架和鏡像加載地址的代碼。

依據(jù)這兩個神器苞氮,我們可以順利的拿到代碼構架湾揽、鏡像加載地址。

Mach-O File Format

Mach-O 是Mach object 的意思笼吟,就是OS X系統(tǒng)中對象文件的存儲格式库物,對象文件包括:

  1. kernel extensions
  • command-line tools
  • applications
  • frameworks
  • libraries (shared and static)

詳細的可以參考Mach-O Programming Topics

一個Mach-O 文件包括下面三個部分

  1. Header: Specifies the target architecture of the file, such as PPC, PPC64, IA-32, or x86-64.
  2. Load commands: Specify the logical structure of the file and the layout of the file in virtual memory.
  3. Raw segment data: Contains raw data for the segments defined in the load commands.

下面是官網(wǎng)上的一張圖形化的Mach-O結構示意圖:


image

下面依次講解這三部分,他們的數(shù)據(jù)結構定義在mach-o/loader.h中贷帮。我們通過三種方式來呈現(xiàn)Mach-O文件結構:

  1. 代碼定義
  • 通過命令行工具otool呈現(xiàn)
  • 通過MachOView呈現(xiàn)戚揭。

這其中otool是系統(tǒng)自帶的對象文件查看工具。MachOView 是網(wǎng)上下載的可視化查看Mach-O結構工具撵枢。由于存在兩個代碼構架民晒,armv7s精居、ARM64,他們的定義稍微有點區(qū)別潜必,僅以ARM64構架為例靴姿。

header

header的數(shù)據(jù)結構的定義如下:

struct mach_header_64 
{
    uint32_t    magic;              ///魔數(shù),標記這個是Mach-O文件
    cpu_type_t    cputype;      ///cup 的類型
    cpu_subtype_t    cpusubtype;
    uint32_t    filetype;    
    uint32_t    ncmds;             /// load commands 個數(shù)
    uint32_t    sizeofcmds;  
    uint32_t    flags;        
    uint32_t    reserved;
};

終端中查看header:

otool -hV ~/Desktop/收集磁滚、解析IOS崩潰日式/Exception/UncaughtException_archive 

輸出如下:

 magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
   MH_MAGIC     ARM         V7  0x00     EXECUTE    23       2432   NOUNDEFS DYLDLINK TWOLEVEL PIE
Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64   ARM64        ALL  0x00     EXECUTE    23       2872   NOUNDEFS DYLDLINK TWOLEVEL PIE

MachOView顯示的結果:


image
  1. magicMH_MAGIC_64佛吓,固定值:0xfeedfacf,標記這是一個Mach-O文件
  • filetype 文件類型是EXECUTE垂攘,可執(zhí)行程序
  • ncmds维雇,load command個數(shù)是23

load Command

load Command 種類特別多,大概有60多種晒他,每種command的數(shù)據(jù)結構是不同的吱型, 不會去一一的說明,只拿LC_SEGMENT陨仅、LC_SYMTAB 做個示例津滞。下面列表了部分load command。

#define    LC_SEGMENT    0x1    /* segment of this file to be mapped */
#define    LC_SYMTAB    0x2    /* link-edit stab symbol table info */
#define    LC_SYMSEG    0x3    /* link-edit gdb symbol table info (obsolete) */
#define    LC_THREAD    0x4    /* thread */
#define    LC_UNIXTHREAD    0x5    /* unix thread (includes a stack) */
#define    LC_LOADFVMLIB    0x6    /* load a specified fixed VM shared library */
.....   

LC_SEGMENT

LC_SEGMENT: segment load command indicates that a part of this file is to be mapped into a 64-bit task’s address space.

說白了掂名,就是映射到內存中的所有數(shù)據(jù)据沈,自然包括代碼、數(shù)據(jù)等等饺蔑。

segment進一步可以分為

  1. **PAGEZERO: 該類型的segment是可執(zhí)行程序的第一個segment锌介,代表指針地址NULL。
  • **TEXT: 就是可執(zhí)行代碼猾警,當然是只讀了
  • **DATA: 可寫的數(shù)據(jù)segment孔祸,應該就是代碼中的變量區(qū)域
  • **OBJC: Objective-C runtime support library
  • **IMPORT
  • **LINKEDIT: contains raw data used by the dynamic linker, such as symbol, string, and relocation table entries。

每種segment可能包含多種類型的內容发皿,例如**TEXT代碼段崔慧,可以有代碼(**text)、字符串(**cstring) 穴墅、常量(**const)惶室、符號(**symbol_stub)、字面量(**literal4玄货、__literal8)皇钞,所以進一步用二級目錄(section)表示。下面是segment松捉、section的數(shù)據(jù)結構:

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 */
};

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 */
};

終端輸入:

otool -lV ~/Desktop/收集夹界、解析IOS崩潰日式/Exception/UncaughtException_archive

輸出:

   ........
   cmd LC_SEGMENT_64
 cmdsize 712
 segname __TEXT
  vmaddr 0x0000000100000000
  vmsize 0x0000000000008000
 fileoff 0
filesize 32768
 maxprot r-x
 initprot r-x
  nsects 8
   flags (none)
   .......

MachOView顯示的結果:


image

圖中直觀的顯示出了LC_SEGMENT的數(shù)據(jù)、LC_SEGMENT的二級目錄section的數(shù)據(jù)隘世。

LC_SYMTAB

LC_SYMTAB的數(shù)據(jù)結構如下:

struct symtab_command {
    uint32_t    cmd;        /* LC_SYMTAB */
    uint32_t    cmdsize;    /* sizeof(struct symtab_command) */
    uint32_t    symoff;        /* symbol table offset */
    uint32_t    nsyms;        /* number of symbol table entries */
    uint32_t    stroff;        /* string table offset */
    uint32_t    strsize;    /* string table size in bytes */
};

終端輸出的結果:

Load command 6
     cmd LC_SYMTAB
 cmdsize 24
  symoff 132944
   nsyms 48
  stroff 133916
 strsize 1152

MachOView看到的結果:


image

LC_SYMTAB 指定了符號的個數(shù)和相對Mach-O的偏移量可柿。

數(shù)據(jù)部分

緊跟著 load command 后面的是數(shù)據(jù)部分鸠踪,就是各個 load command 對應的具體數(shù)據(jù)。

小小結

Mach-O文件的格式非常像一篇文章的結構:

  1. Header部分是文章的摘要复斥,總體描述了非常重要部分营密。
  • Load commands 相當于目錄,Mach-O文件所有內容的索引永票。
  • Raw segment data 正文內容卵贱。

Mach-O 文件格式就是一個規(guī)范滥沫,各個部分都有自己的數(shù)據(jù)格式侣集,內容繁多,只能多看兰绣。
不過之前提到了一個有用的工具—otool世分,查看Mach-O對象文件的命令行工具。

獲取構架缀辩、鏡像加載地址

上面說了那么多Mach-O文件結構臭埋,主要是提供原理支撐,目的是通過對Mach-O文件結構的理解臀玄,找到獲取構架瓢阴、鏡像加載地址的方法。

構架很好獲取健无,就在Mach-O的文件頭中荣恐,獲取的關鍵代碼如下:

/*
 獲取代碼的構架
 */
NSString * getCodeArch()
{
    NSString *strSystemArch =nil;

    ///獲取應用程序的名稱
    NSDictionary *dicInfo =   [[NSBundle mainBundle] infoDictionary];
    if (LJM_Dic_Not_Valid(dicInfo))
    {
        return strSystemArch;
    }
    NSString *strAppName = dicInfo[@"CFBundleName"];
    if (!strAppName)
    {
        return strSystemArch;
    }

    ///獲取  cpu 的大小版本號
    uint32_t count = _dyld_image_count();
    cpu_type_t cpuType = -1;
    cpu_type_t cpuSubType =-1;

    for(uint32_t iImg = 0; iImg < count; iImg++)
    {
        const char* szName = _dyld_get_image_name(iImg);
        if (strstr(szName, strAppName.UTF8String) != NULL)
        {
            const struct mach_header* machHeader = _dyld_get_image_header(iImg);
            cpuType = machHeader->cputype;
            cpuSubType = machHeader->cpusubtype;
            break;
        }
    }

    if(cpuType < 0 ||  cpuSubType <0)
    {
        return  strSystemArch;
    }
    ///轉化cpu 版本為文字類型
    switch(cpuType)
    {
        case CPU_TYPE_ARM:
        {
            strSystemArch = @"arm";
            switch (cpuSubType)
            {
                case CPU_SUBTYPE_ARM_V6:
                    strSystemArch = @"armv6";
                    break;
                case CPU_SUBTYPE_ARM_V7:
                    strSystemArch = @"armv7";
                    break;
                case CPU_SUBTYPE_ARM_V7F:
                    strSystemArch = @"armv7f";
                    break;
                case CPU_SUBTYPE_ARM_V7K:
                    strSystemArch = @"armv7k";
                    break;
#ifdef CPU_SUBTYPE_ARM_V7S
                case CPU_SUBTYPE_ARM_V7S:
                    strSystemArch = @"armv7s";
                    break;
#endif
            }
            break;
        }
#ifdef CPU_TYPE_ARM64
        case CPU_TYPE_ARM64:
            strSystemArch = @"arm64";
            break;
#endif
        case CPU_TYPE_X86:
            strSystemArch = @"i386";
            break;
        case CPU_TYPE_X86_64:
            strSystemArch = @"x86_64";
            break;
    }
    return strSystemArch;
}

主要思路是:通過 _dyld_image_count 獲取到所有的鏡像個數(shù),然后根據(jù)鏡像索引(0…鏡像個數(shù)-1)累贤,依次枚舉出鏡像的名字叠穆,然后,鏡像名字使用_dyld_get_image_header函數(shù)獲取到鏡像的header結構體信息臼膏,賦值到:mach_header* machHeader 中硼被。最后,通過 machHeader->cputype( CPU的類型)和 machHeader->cpusubtype(CPU的子類型)轉化為具體的代碼構架渗磅。

對于鏡像的加載地址嚷硫,其實就是鏡像的header結構體的首地址。詳細代碼如下:

/*
 獲取應用程序的加載地址
 */
NSString * getImageLoadAddress()
{
    NSString *strLoadAddress =nil;

    NSString * strAppName = getAppName();
    if (!strAppName)
    {
        return strLoadAddress;
    }

    ///獲取應用程序的load address
    uint32_t count = _dyld_image_count();
    for(uint32_t iImg = 0; iImg < count; iImg++)
    {
        const char* szName = _dyld_get_image_name(iImg);
        if (strstr(szName, strAppName.UTF8String) != NULL)
        {
            const struct mach_header* header = _dyld_get_image_header(iImg);
            strLoadAddress = [NSString stringWithFormat:@"0x%lX",(uintptr_t)header];
            break;
        }
    }
    return strLoadAddress;
}

主要思路就是:利用_dyld_get_image_header獲取鏡像的header結構體始鱼,header結構體是整個Mach-O的起始部分仔掸,所以,header結構體的首地址就是鏡像的加載地址风响。

好了嘉汰,到目前為止,使用atos符號化崩潰日志的三個條件(符號文件状勤、代碼構架鞋怀、鏡像加載地址)都有了双泪,那么我們就可以完成異常地址的符號化工作了。所以密似,到目前為止焙矛,我們定制的異常系統(tǒng)基本完成了,收集功能残腌、符號化動能都有了村斟。下面來看看我們的系統(tǒng)輸出的內容。

輸出Crash日志

本崩潰收集系統(tǒng)的輸出格式使用 JSON 格式抛猫,輸出的信息包括 arch蟆盹、CrashName、CrashReason闺金、CrashBackTrace逾滥、CrashSystemVersion 。有了這些信息败匹,我們完全可以符號化崩潰地址了寨昙。

{
  "strCrashArch" : "arm64",         ///代碼構架
  "strCrashName" : "NSRangeException",
  "strCrashSystemVersion" : "10.0.2",
  "strCrashReason" : "*** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]",
  "aryCrashBackTrace" : [
    {
      "strStackAddress" : "0x000000018ec6c1d8",
      "strImageName" : "CoreFoundation",
      "strImageLoadAddress" : "<redacted>"
    },
    {
      "strStackAddress" : "0x000000018d6a455c",
      "strImageName" : "libobjc.A.dylib",
      "strImageLoadAddress" : "objc_exception_throw"
    },
    {
      "strStackAddress" : "0x000000018eb48584",
      "strImageName" : "CoreFoundation",
      "strImageLoadAddress" : "CFRunLoopRemoveTimer"
    },
    {
      "strStackAddress" : "0x00000001000b48a0",    ///崩潰地址
      "strImageName" : "UncaughtException",
      "strImageLoadAddress" : "0x1000B0000"       ///鏡像加載地址
    },
    {
      "strStackAddress" : "0x0000000194aea7b0",
      "strImageName" : "UIKit",
      "strImageLoadAddress" : "<redacted>"
    },
    ........
    ........
    {
      "strStackAddress" : "0x0000000194b1b360",
      "strImageName" : "UIKit",
      "strImageLoadAddress" : "UIApplicationMain"
    },
    {
      "strStackAddress" : "0x00000001000b4df0",
      "strImageName" : "UncaughtException",
      "strImageLoadAddress" : "0x1000B0000"
    },
    {
      "strStackAddress" : "0x000000018db285b8",
      "strImageName" : "libdyld.dylib",
      "strImageLoadAddress" : "<redacted>"
    }
  ]
}

小結

這章,我們使用蘋果的API完成了Crash日志收集系統(tǒng)掀亩,這個系統(tǒng)輸出的日志可以使用atos在線下符號化舔哪。同時介紹了Mach-O的文件結構。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末槽棍,一起剝皮案震驚了整個濱河市捉蚤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌刹泄,老刑警劉巖外里,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異特石,居然都是意外死亡盅蝗,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門姆蘸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來墩莫,“玉大人,你說我怎么就攤上這事逞敷】袂兀” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵推捐,是天一觀的道長裂问。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么堪簿? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任痊乾,我火速辦了婚禮,結果婚禮上椭更,老公的妹妹穿的比我還像新娘哪审。我一直安慰自己,他們只是感情好虑瀑,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布湿滓。 她就那樣靜靜地躺著,像睡著了一般舌狗。 火紅的嫁衣襯著肌膚如雪叽奥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天把夸,我揣著相機與錄音而线,去河邊找鬼。 笑死恋日,一個胖子當著我的面吹牛,可吹牛的內容都是我干的嘹狞。 我是一名探鬼主播岂膳,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼磅网!你這毒婦竟也來了谈截?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤涧偷,失蹤者是張志新(化名)和其女友劉穎簸喂,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體燎潮,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡喻鳄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了确封。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片除呵。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖爪喘,靈堂內的尸體忽然破棺而出颜曾,到底是詐尸還是另有隱情,我是刑警寧澤秉剑,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布泛豪,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏诡曙。R本人自食惡果不足惜吕粹,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望岗仑。 院中可真熱鬧匹耕,春花似錦、人聲如沸荠雕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽炸卑。三九已至既鞠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間盖文,已是汗流浹背嘱蛋。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留五续,地道東北人洒敏。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像疙驾,于是被迫代替她去往敵國和親凶伙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內容