應(yīng)用程序加載:從dyld到objc

想要了解應(yīng)用程序加載,我們需要了解下面幾個問題:

  1. 我們寫的代碼是如何加載到內(nèi)存的牙言?
  2. 我們使用的動靜態(tài)庫是如何加載到內(nèi)存的酸钦?
  3. objc是如何啟動的?

我們程序執(zhí)行都會依賴很多庫咱枉,比如UIKit卑硫、CoreFoundation黔寇、libsystem等堕担,這些庫其實就是可執(zhí)行的二進(jìn)制文件,能夠被操作系統(tǒng)加載到內(nèi)存嚣州。庫分為兩種:動態(tài)庫和靜態(tài)庫亿乳。

整個編譯過程如下圖:

編譯過程

首先我們的代碼會經(jīng)歷預(yù)編譯階段硝拧,進(jìn)行詞法和語法樹的分析,然后交給編譯器編譯葛假,會生成相應(yīng)的匯編文件障陶,把這些內(nèi)容鏈接裝載到內(nèi)存,生成我們的可執(zhí)行文件聊训。

動態(tài)庫和靜態(tài)庫區(qū)別一個是動態(tài)鏈接抱究,一個是靜態(tài)鏈接。

動靜態(tài)庫鏈接方式

靜態(tài)鏈接:一個一個裝載進(jìn)內(nèi)存带斑,會有重復(fù)的問題鼓寺,浪費相應(yīng)性能酿雪。
動態(tài)鏈接:并不是直接加載,通過映射加載到內(nèi)存侄刽,內(nèi)存是共享的只會有一份指黎,大部分蘋果的庫都為動態(tài)庫。

靜態(tài)庫和動態(tài)庫是如何加載到內(nèi)存的呢州丹?需要連接器dyld醋安,dyld作用如下圖:

dyld

下面我們開始研究dyld,先在main函數(shù)位置打一個斷點墓毒,執(zhí)行程序:

image

看到函數(shù)調(diào)用棧在main函數(shù)之前會執(zhí)行start方法吓揪,點擊可以看到start就是執(zhí)行dyld中的start方法:

dyld.start

我們再添加一個名字為start的符號斷點,再執(zhí)行程序所计,發(fā)現(xiàn)斷點并沒有斷到start位置

image

為什么沒有停在start柠辞?證明在下層真正調(diào)用不是start而是其他方法。我們知道load方法是在程序執(zhí)行之前就跑主胧,我們添加個load方法并打一個斷點叭首,

load

看函數(shù)調(diào)用棧。點擊最下面的_dyld_start

_dyld_start

dyld源碼可以在官網(wǎng)下載踪栋。
打開源碼工程焙格,首先全局搜索_dyld_start,發(fā)現(xiàn)是匯編實現(xiàn)的夷都,根據(jù)架構(gòu)不同眷唉,代碼不同,我們直接看真機arm64

image

在這里我們分析主要代碼邏輯即可囤官,配合注釋找到start方法調(diào)用:

start

全局搜索dyldbootstrap命名空間冬阳,找到start方法

uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
                const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{

    // Emit kdebug tracepoint to indicate dyld bootstrap has started <rdar://46878536>
    dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);

    // if kernel had to slide dyld, we need to fix up load sensitive locations
    // we have to do this before using any global variables
    rebaseDyld(dyldsMachHeader);

    // kernel sets up env pointer to be just past end of agv array
    const char** envp = &argv[argc+1];
    
    // kernel sets up apple pointer to be just past end of envp array
    const char** apple = envp;
    while(*apple != NULL) { ++apple; }
    ++apple;

    // set up random value for stack canary
    __guard_setup(apple);

#if DYLD_INITIALIZER_SUPPORT
    // run all C++ initializers inside dyld
    runDyldInitializers(argc, argv, envp, apple);
#endif

    _subsystem_init(apple);

    // now that we are done bootstrapping dyld, call dyld's main
    uintptr_t appsSlide = appsMachHeader->getSlide();
    return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}

這里面最重要的就是最后一行,main函數(shù)党饮,點擊進(jìn)去肝陪,發(fā)現(xiàn)main函數(shù)代碼非常多,有將近1000行代碼劫谅,看這種代碼需要點技巧见坑,我們知道這個函數(shù)是有返回值的,那就先看下返回值是什么捏检,有什么對應(yīng)操作荞驴。

返回值是result,賦值的地方有:

  1. result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
  2. result = (uintptr_t)sMainExecutable->getEntryFromLC_UNIXTHREAD();
  3. result = (uintptr_t)&fake_main;

上面三個地方有對result賦值贯城,可以看到最主要就是sMainExecutable相關(guān)處理熊楼,找一下sMainExecutable初始化:

/// instantiate ImageLoader for main executable
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);

看到注釋寫的也很明白,初始化鏡像文件。點擊進(jìn)去:

static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)
{
    // try mach-o loader
//  if ( isCompatibleMachO((const uint8_t*)mh, path) ) {
        ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
        addImage(image);
        return (ImageLoaderMachO*)image;
//  }
    
//  throw "main executable not a known format";
}

點擊進(jìn)入instantiateMainExecutable鲫骗,看到sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd);這個代碼就是按照machO格式進(jìn)行加載文件犬耻。

共享緩存相關(guān)處理:

// load shared cache
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);

加載插入的動態(tài)庫:

// load any inserted libraries
if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
    for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
        loadInsertedDylib(*lib);
}

鏈接主程序:

link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);

鏈接插入的動態(tài)庫:

link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);

弱引用綁定主程序:

// <rdar://problem/12186933> do weak binding only after all inserted images linked
sMainExecutable->weakBind(gLinkContext);

初始化主程序:

// run all initializers
initializeMainExecutable(); 

回調(diào)函數(shù):

// notify any montoring proccesses that this process is about to enter main()
notifyMonitoringDyldMain();

initializeMainExecutable


void initializeMainExecutable()
{
    // record that we've reached this step
    gLinkContext.startedInitializingMainExecutable = true;

    // run initialzers for any inserted dylibs
    ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
    initializerTimes[0].count = 0;
    const size_t rootCount = sImageRoots.size();
    if ( rootCount > 1 ) {
        for(size_t i=1; i < rootCount; ++i) {
            sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
        }
    }
    
    // run initializers for main executable and everything it brings up 
    sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
    
    // register cxa_atexit() handler to run static terminators in all loaded images when this process exits
    if ( gLibSystemHelpers != NULL ) 
        (*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL);

    // dump info if requested
    if ( sEnv.DYLD_PRINT_STATISTICS )
        ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]);
    if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS )
        ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]);
}

首先拿到所有鏡像文件個數(shù),循環(huán)跑起來执泰。點擊進(jìn)入runInitializers看下如何初始化

void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
    uint64_t t1 = mach_absolute_time();
    mach_port_t thisThread = mach_thread_self();
    ImageLoader::UninitedUpwards up;
    up.count = 1;
    up.imagesAndPaths[0] = { this, this->getPath() };
    processInitializers(context, thisThread, timingInfo, up);
    context.notifyBatch(dyld_image_state_initialized, false);
    mach_port_deallocate(mach_task_self(), thisThread);
    uint64_t t2 = mach_absolute_time();
    fgTotalInitTime += (t2 - t1);
}

這里最重要的是processInitializers函數(shù)枕磁,點進(jìn)去

void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread,
                                     InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
{
    uint32_t maxImageCount = context.imageCount()+2;
    ImageLoader::UninitedUpwards upsBuffer[maxImageCount];
    ImageLoader::UninitedUpwards& ups = upsBuffer[0];
    ups.count = 0;
    // Calling recursive init on all images in images list, building a new list of
    // uninitialized upward dependencies.
    for (uintptr_t i=0; i < images.count; ++i) {
        images.imagesAndPaths[i].first->recursiveInitialization(context, thisThread, images.imagesAndPaths[i].second, timingInfo, ups);
    }
    // If any upward dependencies remain, init them.
    if ( ups.count > 0 )
        processInitializers(context, thisThread, timingInfo, ups);
}

還是循環(huán)調(diào)用recursiveInitialization,找到這個方法的實現(xiàn)void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize, InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)术吝,
里面最重要的代碼如圖:

recursiveInitialization

先對依賴文件進(jìn)行初始化计济,然后再初始化自己。notifySingle全局搜索找到這個方法實現(xiàn)排苍,觀察里面最重要代碼:(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());沦寂。
全局搜索sNotifyObjCInit,發(fā)現(xiàn)在registerObjCNotifiers方法中有賦值淘衙,再搜索看哪調(diào)用這個方法:_dyld_objc_notify_register传藏,這個方法和objc源碼中objc_init里面的方法名字是一模一樣的。但其實從dyld加載到objc這條線到現(xiàn)在已經(jīng)斷了彤守,需要再找下別的方法毯侦。

打開objc源碼工程,在源碼中_objc_init打一個斷點遗增,查看函數(shù)調(diào)用棧:

image

從下往上看執(zhí)行流程:

  1. _dyld_start
  2. dyldbootstrap::start
  3. dyld::_main
  4. dyld::initializeMainExecutable()
  5. runInitializers
  6. processInitializers
  7. recursiveInitialization
  8. doInitialization
  9. doModInitFunctions

到這是dyld處理的流程叫惊,在往上看就不知道是什么流程款青,我們現(xiàn)在從上往下看做修,先走libdispatch_os_object_init方法,我們下載libdispatch源碼抡草,全局搜索_os_object_init

void
_os_object_init(void)
{
    _objc_init();
    Block_callbacks_RR callbacks = {
        sizeof(Block_callbacks_RR),
        (void (*)(const void *))&objc_retain,
        (void (*)(const void *))&objc_release,
        (void (*)(const void *))&_os_objc_destructInstance
    };
    _Block_use_RR2(&callbacks);
#if DISPATCH_COCOA_COMPAT
    const char *v = getenv("OBJC_DEBUG_MISSING_POOLS");
    if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v);
    v = getenv("DISPATCH_DEBUG_MISSING_POOLS");
    if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v);
    v = getenv("LIBDISPATCH_DEBUG_MISSING_POOLS");
    if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v);
#endif
}

可以看到_os_object_init確實調(diào)用了_objc_init饰及,再全局搜索_os_object_init,發(fā)現(xiàn)是在libdispatch_init有調(diào)用康震,再全局搜索libdispatch_init發(fā)現(xiàn)沒有調(diào)用地方燎含,看上面截圖函數(shù)調(diào)用棧,libSystem調(diào)用過來的腿短,我們下載libSystem源碼屏箍,打開libSystem源碼工程全局搜索libdispatch_init,發(fā)現(xiàn)是libSystem_initializer方法進(jìn)行調(diào)用橘忱,而libSystem_initializer這個方法沒有調(diào)用赴魁,看函數(shù)調(diào)用棧回到dyld源碼進(jìn)行搜索钝诚,沒找到相關(guān)信息颖御,全局搜索doModInitFunctions看這個方法實現(xiàn),可以看到框住就是調(diào)用libSystem_initializer相關(guān)的方法:

image

doModInitFunctions是由doInitialization方法調(diào)用凝颇,最后找到位置:

recursiveInitialization

到此潘拱,整個項目從dyld如何加載到objc_init的流程已經(jīng)分析完了疹鳄。
我們可以看到_dyld_objc_notify_register(&map_images, load_images, unmap_image);方法調(diào)用,map_imagesload_image都是參數(shù)芦岂,那么他們什么時候調(diào)用的呢瘪弓,時機肯定在別的地方,繼續(xù)探索禽最。

之前分析notifySingle調(diào)用sNotifyObjCInit杠茬,找到sNotifyObjCInit賦值的地方,

sNotifyObjCInit賦值

_dyld_objc_notify_register調(diào)用的registerObjCNotifiers弛随,也就是說_dyld_objc_notify_register會對三個參數(shù)進(jìn)行初始化瓢喉,也就是說notifySingle會對將要執(zhí)行的方法進(jìn)行初始化賦值,但是具體調(diào)用還不知道舀透。

之前我們看到有_dyld_objc_notify_register方法:

void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped)
{
    dyld::registerObjCNotifiers(mapped, init, unmapped);
}

這個方法三個參數(shù)對應(yīng)著objc源碼中同名方法的map_images栓票、load_imagesumap_image愕够,進(jìn)去看賦值:

void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
    // record functions to call
    sNotifyObjCMapped   = mapped;
    sNotifyObjCInit     = init;
    sNotifyObjCUnmapped = unmapped;

    // call 'mapped' function with all images mapped so far
    try {
        notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
    }
    catch (const char* msg) {
        // ignore request to abort during registration
    }

    // <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)
    for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
        ImageLoader* image = *it;
        if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
            dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
            (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
        }
    }
}

也就是我們現(xiàn)在需要找sNotifyObjCMapped()走贪、sNotifyObjCInit()sNotifyObjCUnmapped()惑芭,這三個函數(shù)調(diào)用才會執(zhí)行我們最關(guān)心的map_images坠狡、load_imagesumap_image遂跟。

  1. 全局搜索sNotifyObjCMapped找調(diào)用的地方逃沿,發(fā)現(xiàn)_dyld_objc_notify_register->registerObjCNotifiers->notifyBatchPartial->sNotifyObjCMapped這個流程會調(diào)用,也就是函數(shù)執(zhí)行時候幻锁,就會執(zhí)行第一個參數(shù)對應(yīng)的方法凯亮。
  2. 全局搜索sNotifyObjCInit,有兩個調(diào)用地方哄尔,a(dyld_image_state_initialized):_dyld_objc_notify_register->registerObjCNotifiers->sNotifyObjCInit()假消,加載自己的庫的時候會直接調(diào)用load_images。b(dyld_image_state_dependents_initialized):notifySingle->sNotifyObjCInit()岭接,加載自己依賴庫的時候富拗,依賴庫是通過notifySingle來調(diào)用load_images
  3. 全局搜索sNotifyObjCUnmapped鸣戴,發(fā)現(xiàn)removeImage才會調(diào)用啃沪,移除鏡像文件時候,暫時不深入分析葵擎。

總結(jié)下從dyld開始怎么加載到objc以及map_imagesload_images調(diào)用的流程圖:

dyld啟動到objc_init.jpg

load C++ main 執(zhí)行順序

接下來我們再看一個有意思的現(xiàn)象谅阿,在main函數(shù)中輸出Hello, World!,并且在main函數(shù)中添加一個C++方法:

int main(int argc, const char * argv[]) {
    @autoreleasepool {        
        SJLog(@"Hello, World!");
    }
    return 0;
}

__attribute__  ((constructor)) void sjFunc() {
    printf("C++ 來了  %s \n", __func__);
}

添加SJPerson類,并實現(xiàn)load方法:

@implementation SJPerson

+ (void)load
{
    SJLog(@"%s", __func__);
}

@end

運行程序签餐,可以看到打印結(jié)果如下:

image

首先執(zhí)行load方法寓涨,然后執(zhí)行C++方法,最后才會走到main函數(shù)里面氯檐。
研究方法執(zhí)行順序戒良,首先我們看下load方法是怎么調(diào)用:

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        loadAllCategories();
    }

    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}

首先會prepare_load_methods準(zhǔn)備所有的load方法,

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertLocked();

    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }

    category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        if (!cls) continue;  // category for ignored weak-linked class
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        realizeClassWithoutSwift(cls, nil);
        ASSERT(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
}

這里實現(xiàn)準(zhǔn)備和調(diào)用類冠摄、分類的load方法糯崎。
下一步C++方法為什么會調(diào)用呢?在C++方法打個斷點河泳,調(diào)試打印函數(shù)調(diào)用棧:

C++函數(shù)調(diào)用棧

dyld源碼中查看doInitialization函數(shù)調(diào)用:

image

這個位置我們很熟悉了吧沃呢,上面已經(jīng)解釋過了,也就是doInitialization會調(diào)用我們寫的C++方法拆挥。

在搞一個騷操作薄霜,我們在objc-os.mm文件中,添加一個C++方法纸兔,再執(zhí)行程序:

__attribute__  ((constructor)) void sjFunc() {
    printf("objc C++ 來了  %s \n", __func__);
}

看到執(zhí)行的結(jié)果:


image

也就是說惰瓜,在同一個鏡像文件中,才會先執(zhí)行load方法汉矿,再執(zhí)行C++方法崎坊。

執(zhí)行完dyld然后是如何走到我們工程里面的main函數(shù)呢?
在C++方法打斷點洲拇,進(jìn)入斷點后顯示匯編:

image

跳出這個方法:

image

讀取寄存器:

register

匯編最后執(zhí)行jump rax奈揍,rax寄存器存的就是我們工程的main函數(shù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末呻待,一起剝皮案震驚了整個濱河市打月,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蚕捉,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件柴淘,死亡現(xiàn)場離奇詭異迫淹,居然都是意外死亡,警方通過查閱死者的電腦和手機为严,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門敛熬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人第股,你說我怎么就攤上這事应民。” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵诲锹,是天一觀的道長繁仁。 經(jīng)常有香客問我,道長归园,這世上最難降的妖魔是什么黄虱? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮庸诱,結(jié)果婚禮上捻浦,老公的妹妹穿的比我還像新娘。我一直安慰自己桥爽,他們只是感情好朱灿,可當(dāng)我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著钠四,像睡著了一般母剥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上形导,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天环疼,我揣著相機與錄音,去河邊找鬼朵耕。 笑死炫隶,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的阎曹。 我是一名探鬼主播伪阶,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼处嫌!你這毒婦竟也來了栅贴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤熏迹,失蹤者是張志新(化名)和其女友劉穎檐薯,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體注暗,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡坛缕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了捆昏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赚楚。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖骗卜,靈堂內(nèi)的尸體忽然破棺而出宠页,到底是詐尸還是另有隱情左胞,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布举户,位于F島的核電站烤宙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏敛摘。R本人自食惡果不足惜门烂,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望兄淫。 院中可真熱鬧屯远,春花似錦、人聲如沸捕虽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泄私。三九已至房揭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間晌端,已是汗流浹背捅暴。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留咧纠,地道東北人蓬痒。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓漆羔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親演痒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,630評論 2 359

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