iOS app啟動(dòng) - dyld加載App流程

前言

iPhone 上啟動(dòng)app都是由dyld調(diào)度的掷贾,現(xiàn)在我們簡(jiǎn)單來(lái)窺探dyld的究竟屈藐。
dyld源碼

__dyld_start 開(kāi)始


app啟動(dòng)前會(huì)先加載dyld略就,然后通過(guò)dyld去執(zhí)行__dyld_start,下面我列下arm的實(shí)現(xiàn)

#if __arm__
    .text
    .align 2
__dyld_start:
    mov r8, sp      // save stack pointer
    sub sp, #16     // make room for outgoing parameters
    bic     sp, sp, #15 // force 16-byte alignment

    // call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
    ldr r0, [r8]    // r0 = mach_header
    ldr r1, [r8, #4]    // r1 = argc
    add r2, r8, #8  // r2 = argv
    adr r3, __dyld_start
    sub r3 ,r3, #0x1000 // r3 = dyld_mh
    add r4, sp, #12
    str r4, [sp, #0]    // [sp] = &startGlue

    bl  __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
    ldr r5, [sp, #12]
    cmp r5, #0
    bne Lnew

    // traditional case, clean up stack and jump to result
    add sp, r8, #4  // remove the mach_header argument.
    bx  r0      // jump to the program's entry point

    // LC_MAIN case, set up stack for call to main()
Lnew:   mov lr, r5          // simulate return address into _start in libdyld
    mov r5, r0          // save address of main() for later use
    ldr r0, [r8, #4]        // main param1 = argc
    add r1, r8, #8      // main param2 = argv
    add r2, r1, r0, lsl #2
    add r2, r2, #4      // main param3 = &env[0]
    mov r3, r2
Lapple: ldr r4, [r3]
    add r3, #4
    cmp r4, #0
    bne Lapple          // main param4 = apple
    bx  r5

#endif /* __arm__ */

知道大部分人看匯編不是很懂般卑,不過(guò)Apple的開(kāi)發(fā)人員也是寫(xiě)了注釋的啊,在這一段我們知道接下來(lái)我們會(huì)調(diào)用到dyldbootstrap::start的方法

//在dyldinitialization.cpp中
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

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

前面都是些準(zhǔn)備工作酿秸,我們先略過(guò),這的核心其實(shí)就是dyld::_main的調(diào)用

dyld::_main 分析


第一步 : 設(shè)置app運(yùn)行的上下文

image.png
static void setContext(const macho_header* mainExecutableMH, int argc, const char* argv[], const char* envp[], const char* apple[])
{
    gLinkContext.loadLibrary            = &libraryLocator;
    gLinkContext.terminationRecorder    = &terminationRecorder;
    gLinkContext.flatExportFinder       = &flatFindExportedSymbol;
    gLinkContext.coalescedExportFinder  = &findCoalescedExportedSymbol;
    gLinkContext.getCoalescedImages     = &getCoalescedImages;
    gLinkContext.undefinedHandler       = &undefinedHandler;
    gLinkContext.getAllMappedRegions    = &getMappedRegions;
    gLinkContext.bindingHandler         = NULL;
    gLinkContext.notifySingle           = &notifySingle;
    gLinkContext.notifyBatch            = &notifyBatch;
    gLinkContext.removeImage            = &removeImage;
    gLinkContext.registerDOFs           = dyld3::Loader::dtraceUserProbesEnabled() ? &registerDOFs : NULL;
    gLinkContext.clearAllDepths         = &clearAllDepths;
    gLinkContext.printAllDepths         = &printAllDepths;
    gLinkContext.imageCount             = &imageCount;
    gLinkContext.setNewProgramVars      = &setNewProgramVars;
    gLinkContext.inSharedCache          = &inSharedCache;
    gLinkContext.setErrorStrings        = &setErrorStrings;
#if SUPPORT_OLD_CRT_INITIALIZATION
    gLinkContext.setRunInitialzersOldWay= &setRunInitialzersOldWay;
#endif
    gLinkContext.findImageContainingAddress = &findImageContainingAddress;
    gLinkContext.addDynamicReference    = &addDynamicReference;
#if SUPPORT_ACCELERATE_TABLES
    gLinkContext.notifySingleFromCache  = &notifySingleFromCache;
    gLinkContext.getPreInitNotifyHandler= &getPreInitNotifyHandler;
    gLinkContext.getBoundBatchHandler   = &getBoundBatchHandler;
#endif
    gLinkContext.bindingOptions         = ImageLoader::kBindingNone;
    gLinkContext.argc                   = argc;
    gLinkContext.argv                   = argv;
    gLinkContext.envp                   = envp;
    gLinkContext.apple                  = apple;
    gLinkContext.progname               = (argv[0] != NULL) ? basename(argv[0]) : "";
    gLinkContext.programVars.mh         = mainExecutableMH;
    gLinkContext.programVars.NXArgcPtr  = &gLinkContext.argc;
    gLinkContext.programVars.NXArgvPtr  = &gLinkContext.argv;
    gLinkContext.programVars.environPtr = &gLinkContext.envp;
    gLinkContext.programVars.__prognamePtr=&gLinkContext.progname;
    gLinkContext.mainExecutable         = NULL;
    gLinkContext.imageSuffix            = NULL;
    gLinkContext.dynamicInterposeArray  = NULL;
    gLinkContext.dynamicInterposeCount  = 0;
    gLinkContext.prebindUsage           = ImageLoader::kUseAllPrebinding;
    gLinkContext.sharedRegionMode       = ImageLoader::kUseSharedRegion;
}

第二步:映射共享緩存

image.png
static void mapSharedCache()
{
    dyld3::SharedCacheOptions opts;
    opts.cacheDirOverride   = sSharedCacheOverrideDir;
    opts.forcePrivate       = (gLinkContext.sharedRegionMode == ImageLoader::kUsePrivateSharedRegion);


#if __x86_64__ && !TARGET_OS_SIMULATOR
    opts.useHaswell         = sHaswell;
#else
    opts.useHaswell         = false;
#endif
    opts.verbose            = gLinkContext.verboseMapping;
    loadDyldCache(opts, &sSharedCacheLoadInfo);

    // update global state
    if ( sSharedCacheLoadInfo.loadAddress != nullptr ) {
        gLinkContext.dyldCache                              = sSharedCacheLoadInfo.loadAddress;
        dyld::gProcessInfo->processDetachedFromSharedRegion = opts.forcePrivate;
        dyld::gProcessInfo->sharedCacheSlide                = sSharedCacheLoadInfo.slide;
        dyld::gProcessInfo->sharedCacheBaseAddress          = (unsigned long)sSharedCacheLoadInfo.loadAddress;
        sSharedCacheLoadInfo.loadAddress->getUUID(dyld::gProcessInfo->sharedCacheUUID);
        dyld3::kdebug_trace_dyld_image(DBG_DYLD_UUID_SHARED_CACHE_A, sSharedCacheLoadInfo.path, (const uuid_t *)&dyld::gProcessInfo->sharedCacheUUID[0], {0,0}, {{ 0, 0 }}, (const mach_header *)sSharedCacheLoadInfo.loadAddress);
    }

//#if __IPHONE_OS_VERSION_MIN_REQUIRED && !TARGET_OS_SIMULATOR
// RAM disk booting does not have shared cache yet
// Don't make lack of a shared cache fatal in that case
//  if ( sSharedCacheLoadInfo.loadAddress == nullptr ) {
//      if ( sSharedCacheLoadInfo.errorMessage != nullptr )
//          halt(sSharedCacheLoadInfo.errorMessage);
//      else
//          halt("error loading dyld shared cache");
//  }
//#endif
}

iOS為了控制ipa包的大小嵌言,啟用了共享緩存技術(shù);對(duì)于一些公共的庫(kù)(如UIKit,CoreFundation)嗅回,每一個(gè)手機(jī)所有的app其實(shí)用的都是一樣的,所以沒(méi)必要多次加載摧茴。當(dāng)app進(jìn)程啟動(dòng)開(kāi)始時(shí)绵载,dyld首先檢查Mach-O和所需的動(dòng)態(tài)庫(kù)是否存在共享緩存中,如果存在苛白,則直接將共享緩存中的庫(kù)直接映射到內(nèi)存中娃豹。

第三步:實(shí)例化主程序

image.png
// The kernel maps in main executable before dyld gets control.  We need to 
// make an ImageLoader* for the already mapped in main executable.
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";
}
//....
// create image for main executable
ImageLoader* ImageLoaderMachO::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, const LinkContext& context)
{
    //dyld::log("ImageLoader=%ld, ImageLoaderMachO=%ld, ImageLoaderMachOClassic=%ld, ImageLoaderMachOCompressed=%ld\n",
    //  sizeof(ImageLoader), sizeof(ImageLoaderMachO), sizeof(ImageLoaderMachOClassic), sizeof(ImageLoaderMachOCompressed));
    bool compressed;
    unsigned int segCount;
    unsigned int libCount;
    const linkedit_data_command* codeSigCmd;
    const encryption_info_command* encryptCmd;
    sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd);
    // instantiate concrete class based on content of load commands
    if ( compressed ) 
        return ImageLoaderMachOCompressed::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
    else
#if SUPPORT_CLASSIC_MACHO
        return ImageLoaderMachOClassic::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
#else
        throw "missing LC_DYLD_INFO load command";
#endif
}

這里會(huì)根據(jù)代碼是否壓縮過(guò)做響應(yīng)的處理,如果壓縮過(guò)
ImageLoaderMachOCompressed::instantiateMainExecutable,否則
ImageLoaderMachOClassic::instantiateMainExecutable

第四步:加載插入動(dòng)態(tài)庫(kù)

image.png
static void loadInsertedDylib(const char* path)
{
    unsigned cacheIndex;
    try {
        LoadContext context;
        context.useSearchPaths      = false;
        context.useFallbackPaths    = false;
        context.useLdLibraryPath    = false;
        context.implicitRPath       = false;
        context.matchByInstallName  = false;
        context.dontLoad            = false;
        context.mustBeBundle        = false;
        context.mustBeDylib         = true;
        context.canBePIE            = false;
        context.origin              = NULL; // can't use @loader_path with DYLD_INSERT_LIBRARIES
        context.rpath               = NULL;
        load(path, context, cacheIndex);
    }
    catch (const char* msg) {
        if ( gLinkContext.allowInsertFailures )
            dyld::log("dyld: warning: could not load inserted library '%s' into hardened process because %s\n", path, msg);
        else
            halt(dyld::mkstringf("could not load inserted library '%s' because %s\n", path, msg));
    }
    catch (...) {
        halt(dyld::mkstringf("could not load inserted library '%s'\n", path));
    }
}

第五步:鏈接主程序

image.png

這里修正符號(hào)綁定

第六步:鏈接插入動(dòng)態(tài)庫(kù)

image.png

第七步:弱綁定引用主程序

image.png

第八步:初始化主程序

image.png
//第一部分 
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]);
}
//...第二部分
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);
}
//...第三部分
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);
}
//....第四部分
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
                                          InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
    recursive_lock lock_info(this_thread);
    recursiveSpinLock(lock_info);

    if ( fState < dyld_image_state_dependents_initialized-1 ) {
        uint8_t oldState = fState;
        // break cycles
        fState = dyld_image_state_dependents_initialized-1;
        try {
            // initialize lower level libraries first
            for(unsigned int i=0; i < libraryCount(); ++i) {
                ImageLoader* dependentImage = libImage(i);
                if ( dependentImage != NULL ) {
                    // don't try to initialize stuff "above" me yet
                    if ( libIsUpward(i) ) {
                        uninitUps.imagesAndPaths[uninitUps.count] = { dependentImage, libPath(i) };
                        uninitUps.count++;
                    }
                    else if ( dependentImage->fDepth >= fDepth ) {
                        dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps);
                    }
                }
            }
            
            // record termination order
            if ( this->needsTermination() )
                context.terminationRecorder(this);

            // let objc know we are about to initialize this image
            uint64_t t1 = mach_absolute_time();
            fState = dyld_image_state_dependents_initialized;
            oldState = fState;
            context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
            
            // initialize this image
            bool hasInitializers = this->doInitialization(context);

            // let anyone know we finished initializing this image
            fState = dyld_image_state_initialized;
            oldState = fState;
            context.notifySingle(dyld_image_state_initialized, this, NULL);
            
            if ( hasInitializers ) {
                uint64_t t2 = mach_absolute_time();
                timingInfo.addTime(this->getShortName(), t2-t1);
            }
        }
        catch (const char* msg) {
            // this image is not initialized
            fState = oldState;
            recursiveSpinUnLock();
            throw;
        }
    }
    
    recursiveSpinUnLock();
}
//
bool ImageLoaderMachO:doInitialization(const LinkContext& context)
{
    CRSetCrashLogMessage2(this->getPath());

    // mach-o has -init and static initializers
    doImageInit(context);
    doModInitFunctions(context);
    
    CRSetCrashLogMessage2(NULL);
    
    return (fHasDashInit || fHasInitializers);
}

這里我已經(jīng)將代碼調(diào)用的重點(diǎn)函數(shù)列出來(lái)出來(lái)

  • initializeMainExecutable:初始化主程序,內(nèi)部會(huì)調(diào)用其他跟鏡像rootImage的初始化
  • runInitializers: 進(jìn)一步調(diào)用processInitializers
  • processInitializers:遞歸調(diào)用鏡像的初始化
  • recursiveInitialization: 這里會(huì)檢測(cè)一些依賴(lài)购裙,并將它初始化懂版,接著開(kāi)始doInitialization
  • doInitialization:開(kāi)始初始化動(dòng)作,這會(huì)進(jìn)行各個(gè)模塊的初始化操作_objc_init也是在此進(jìn)行的
void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    runtime_init();
    exception_init();
    cache_init();
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

第九步:通知monitoringDyld,將要進(jìn)入主 main入口

notifyMonitoringDyldMain();
....
// find entry point for main executable
            result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();

第十步:進(jìn)入main函數(shù)

image.png

總結(jié)

  • 起點(diǎn):__dyld_start:
  • 中間_main經(jīng)過(guò)

1缓窜、 設(shè)置app運(yùn)行的上下文
2定续、映射共享緩存mapShareCache
3、實(shí)例化主程序instantiateFromLoadedImage
4禾锤、加載插入動(dòng)態(tài)庫(kù)loadInsertedDylib
5、鏈接主程序
6摹察、鏈接插入動(dòng)態(tài)庫(kù)
7恩掷、弱綁定引用主程序
8、初始化主程序initializeMainExecutable
9供嚎、通知monitoringDyld黄娘,將要進(jìn)入主 main入口
10、進(jìn)入app運(yùn)行的main函數(shù)

?著作權(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)離奇詭異着帽,居然都是意外死亡杂伟,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)仍翰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)赫粥,“玉大人,你說(shuō)我怎么就攤上這事予借≡狡剑” “怎么了频蛔?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)秦叛。 經(jīng)常有香客問(wèn)我帽驯,道長(zhǎng),這世上最難降的妖魔是什么书闸? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任尼变,我火速辦了婚禮,結(jié)果婚禮上浆劲,老公的妹妹穿的比我還像新娘嫌术。我一直安慰自己,他們只是感情好牌借,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布度气。 她就那樣靜靜地躺著,像睡著了一般膨报。 火紅的嫁衣襯著肌膚如雪磷籍。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,394評(píng)論 1 310
  • 那天现柠,我揣著相機(jī)與錄音院领,去河邊找鬼。 笑死够吩,一個(gè)胖子當(dāng)著我的面吹牛比然,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播周循,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼强法,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了湾笛?” 一聲冷哼從身側(cè)響起饮怯,我...
    開(kāi)封第一講書(shū)人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嚎研,沒(méi)想到半個(gè)月后蓖墅,有當(dāng)?shù)厝嗽跇?shù)林里發(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
  • 文/蒙蒙 一章姓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧识埋,春花似錦凡伊、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至惠豺,卻和暖如春银还,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背洁墙。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工蛹疯, 沒(méi)想到剛下飛機(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