iOS dyld调用流程

一、dyld概述

dyldthe dynamic link editor)动态链接器,是苹果操作系统一个重要组成部分,在系统内核做好程序准备工作之后交由dyld负责余下的工作。

二、dyld 源码分析

新建一个工程,写一个+load方法,分别在+loadmain函数上打断点,然后运行在真机上。

image.png

这个时候函数的调用栈如下:


image.png

汇编代码如下:


image.png

dyld源码中(dyld-832.7.3)找到dyldbootstrap::start函数(dyldInitialization.cpp),这也就是函数开始的地方。

接下来结合源码分析怎么从start调用到loadmain方法。

2.1dyldbootstrap::start(dyldInitialization.cpp

可以通过搜索dyldbootstrap命名空间找到start源码。核心代码如下:

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

    //告诉debug server dyld启动
    // 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
    //重定位dyld
    rebaseDyld(dyldsMachHeader);

    //栈溢出保护
    __guard_setup(apple);

    //初始化dyld
    _subsystem_init(apple);

    //调用dyld main函数
    return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}

start主要做了以下几件事:

  • 告诉debug server dyld启动
  • 重定位dyld
  • 栈溢出保护
  • 初始化dyld
  • 调用dyld main函数

其中start只做了一些配置和初始化的工作,核心逻辑在main函数中,start返回了main函数的返回值。

2.1.1dyld::_main(dyld2.cpp)

核心逻辑如下:

uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{
    //内核检测代码
    if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) {
        launchTraceID = dyld3::kdebug_trace_dyld_duration_start(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, (uint64_t)mainExecutableMH, 0, 0);
    }

    //Check and see if there are any kernel flags
    dyld3::BootArgs::setFlags(hexToUInt64(_simple_getenv(apple, "dyld_flags"), nullptr));

……

    // Grab the cdHash of the main executable from the environment
    //主程序可执行文件
    uint8_t mainExecutableCDHashBuffer[20];
    const uint8_t* mainExecutableCDHash = nullptr;
    if ( const char* mainExeCdHashStr = _simple_getenv(apple, "executable_cdhash") ) {
        unsigned bufferLenUsed;
        if ( hexStringToBytes(mainExeCdHashStr, mainExecutableCDHashBuffer, sizeof(mainExecutableCDHashBuffer), bufferLenUsed) )
            mainExecutableCDHash = mainExecutableCDHashBuffer;
    }
    //获取主程序Header,Slide(ASLR的偏移值)
    getHostInfo(mainExecutableMH, mainExecutableSlide);

……

    uintptr_t result = 0;
    sMainExecutableMachHeader = mainExecutableMH;
    sMainExecutableSlide = mainExecutableSlide;


……


    CRSetCrashLogMessage("dyld: launch started");
    //设置上下文,将信息放入 gLinkContext 中
    setContext(mainExecutableMH, argc, argv, envp, apple);

    // Pickup the pointer to the exec path.
    sExecPath = _simple_getenv(apple, "executable_path");

    // <rdar://problem/13868260> Remove interim apple[0] transition code from dyld
    if (!sExecPath) sExecPath = apple[0];

……

    //根据环境变量 envp 配置进程是否受限制,AMFI相关(Apple Mobile File Integrity苹果移动文件保护)
    configureProcessRestrictions(mainExecutableMH, envp);

……

#if TARGET_OS_OSX
    if ( !gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache ) {
        pruneEnvironmentVariables(envp, &apple);
        // set again because envp and apple may have changed or moved
        //又设置一次上下文,在文件受限的时候可能更改了envp。
        setContext(mainExecutableMH, argc, argv, envp, apple);
    }
    else
#endif
    {
        //检测环境变量并设置默认值,这个时候还没有加载数据。
        checkEnvironmentVariables(envp);
        defaultUninitializedFallbackPaths(envp);
    }
……
    //打印环境变量,可以在"Scheme -> Arguments -> Environment Variables"中配置
    if ( sEnv.DYLD_PRINT_OPTS )
        printOptions(argv);
    if ( sEnv.DYLD_PRINT_ENV ) 
        printEnvironmentVariables(envp);

……

    // load shared cache,加载共享缓存,只读了主程序还没有加载主程序。iOS必须有共享缓存。
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
    if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
#if TARGET_OS_SIMULATOR
        if ( sSharedCacheOverrideDir)
            mapSharedCache(mainExecutableSlide);
#else
        //加载共享缓存方法
        mapSharedCache(mainExecutableSlide);
#endif
    }

……


#if !TARGET_OS_SIMULATOR
    //dyld3 ClosureMode模式,iOS11引入ClosureMode,iOS13后动态库和三方库都使用ClosureMode加载。
    if ( sClosureMode == ClosureMode::Off ) {
    //ClosureMode off打印log,往 if-else 后面走了
        if ( gLinkContext.verboseWarnings )
            dyld::log("dyld: not using closures\n");
    } else {
    //ClosureMode on
        //启动模式 闭包模式 DYLD_LAUNCH_MODE_USING_CLOSURE
        sLaunchModeUsed = DYLD_LAUNCH_MODE_USING_CLOSURE;
        const dyld3::closure::LaunchClosure* mainClosure = nullptr;
        //主程序 info 和 Header
        dyld3::closure::LoadedFileInfo mainFileInfo;
        mainFileInfo.fileContent = mainExecutableMH;
        mainFileInfo.path = sExecPath;
……
        //第一次从共享缓存找闭包
        if ( sSharedCacheLoadInfo.loadAddress != nullptr ) {
            //先从共享缓存找实例闭包
            mainClosure = sSharedCacheLoadInfo.loadAddress->findClosure(sExecPath);
            if ( gLinkContext.verboseWarnings && (mainClosure != nullptr) )
                dyld::log("dyld: found closure %p (size=%lu) in dyld shared cache\n", mainClosure, mainClosure->size());
            if ( mainClosure != nullptr )
                //如果拿到设置状态
                sLaunchModeUsed |= DYLD_LAUNCH_MODE_CLOSURE_FROM_OS;
        }

……

        //拿到闭包 && 验证闭包,如果闭包失效
        if ( (mainClosure != nullptr) && !closureValid(mainClosure, mainFileInfo, mainExecutableCDHash, true, envp) ) {
            mainClosure = nullptr;
            //闭包失效设置状态
            sLaunchModeUsed &= ~DYLD_LAUNCH_MODE_CLOSURE_FROM_OS;
        }

……
        // If we didn't find a valid cache closure then try build a new one
        //判断mainClosure是否为空
        if ( (mainClosure == nullptr) && allowClosureRebuilds ) {
            // if forcing closures, and no closure in cache, or it is invalid, check for cached closure
            if ( !sForceInvalidSharedCacheClosureFormat )
                //缓存中找
                mainClosure = findCachedLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
            if ( mainClosure == nullptr ) {
                // if  no cached closure found, build new one
                //缓存中找不到则创建一个,一直拿 mainClosure 是为了拿他创建主程序。
                mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
                if ( mainClosure != nullptr )
                    //创建失败则设置状态
                    sLaunchModeUsed |= DYLD_LAUNCH_MODE_BUILT_CLOSURE_AT_LAUNCH;
            }
        }

        // exit dyld after closure is built, without running program
        if ( sJustBuildClosure )
            _exit(EXIT_SUCCESS);

        // try using launch closure
        if ( mainClosure != nullptr ) {
            CRSetCrashLogMessage("dyld3: launch started");
            if ( mainClosure->topImage()->fixupsNotEncoded() )
                sLaunchModeUsed |= DYLD_LAUNCH_MODE_MINIMAL_CLOSURE;
            Diagnostics diag;
            bool closureOutOfDate;
            bool recoverable;
            //启动主程序,mainClosure 相当于加载器
            bool launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
                                              mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable);
            //启动失败或者过期 允许重建
            if ( !launched && closureOutOfDate && allowClosureRebuilds ) {
                // closure is out of date, build new one
                //再创建一个
                mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
                if ( mainClosure != nullptr ) {
                    diag.clearError();
                    sLaunchModeUsed |= DYLD_LAUNCH_MODE_BUILT_CLOSURE_AT_LAUNCH;
                    if ( mainClosure->topImage()->fixupsNotEncoded() )
                        sLaunchModeUsed |= DYLD_LAUNCH_MODE_MINIMAL_CLOSURE;
                    else
                        sLaunchModeUsed &= ~DYLD_LAUNCH_MODE_MINIMAL_CLOSURE;
                    //启动
                    launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
                                                 mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable);
                }
            }
            if ( launched ) {
                //启动成功保存白能量,主程序加载成功
                gLinkContext.startedInitializingMainExecutable = true;
                if (sSkipMain)
                    //主程序main函数,dyld的main执行完毕返回主程序的main
                    result = (uintptr_t)&fake_main;
                return result;
            }
            else {
                //失败报错
                if ( gLinkContext.verboseWarnings ) {
                    dyld::log("dyld: unable to use closure %p\n", mainClosure);
                }
                if ( !recoverable )
                    halt(diag.errorMessage());
            }
        }
    }
#endif // TARGET_OS_SIMULATOR
    // could not use closure info, launch old way
    //dyld2模式
    sLaunchModeUsed = 0;


    // install gdb notifier
    //两个回调地址放入stateToHandlers数组中
    stateToHandlers(dyld_image_state_dependents_mapped, sBatchHandlers)->push_back(notifyGDB);
    stateToHandlers(dyld_image_state_mapped, sSingleHandlers)->push_back(updateAllImages);
    // make initial allocations large enough that it is unlikely to need to be re-alloced
    //分配初始化空间,尽可能大一些保证后面够用。
    sImageRoots.reserve(16);
    sAddImageCallbacks.reserve(4);
    sRemoveImageCallbacks.reserve(4);
    sAddLoadImageCallbacks.reserve(4);
    sImageFilesNeedingTermination.reserve(16);
    sImageFilesNeedingDOFUnregistration.reserve(8);

……

    try {
        // add dyld itself to UUID list
        //dyld加入uuid列表
        addDyldImageToUUIDList();

#if SUPPORT_ACCELERATE_TABLES
#if __arm64e__
        // Disable accelerator tables when we have threaded rebase/bind, which is arm64e executables only for now.
        if ((sMainExecutableMachHeader->cpusubtype & ~CPU_SUBTYPE_MASK) == CPU_SUBTYPE_ARM64E)
            sDisableAcceleratorTables = true;
#endif
        //主程序还没有rebase
        bool mainExcutableAlreadyRebased = false;
        if ( (sSharedCacheLoadInfo.loadAddress != nullptr) && !dylibsCanOverrideCache() && !sDisableAcceleratorTables && (sSharedCacheLoadInfo.loadAddress->header.accelerateInfoAddr != 0) ) {
            struct stat statBuf;
            if ( dyld3::stat(IPHONE_DYLD_SHARED_CACHE_DIR "no-dyld2-accelerator-tables", &statBuf) != 0 )
                sAllCacheImagesProxy = ImageLoaderMegaDylib::makeImageLoaderMegaDylib(&sSharedCacheLoadInfo.loadAddress->header, sSharedCacheLoadInfo.slide, mainExecutableMH, gLinkContext);
        }
//加载所有的可执行文件 image list
reloadAllImages:
#endif


……


        CRSetCrashLogMessage(sLoadingCrashMessage);
        // instantiate ImageLoader for main executable
        //实例化主程序,加入到allImages(第一个靠dyld加载的image就是主程序)
        sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
        gLinkContext.mainExecutable = sMainExecutable;
        //代码签名
        gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);

#if TARGET_OS_SIMULATOR
        // check main executable is not too new for this OS
        //检查主程序是否属于当前系统
        {
            if ( ! isSimulatorBinary((uint8_t*)mainExecutableMH, sExecPath) ) {
                throwf("program was built for a platform that is not supported by this runtime");
            }
            uint32_t mainMinOS = sMainExecutable->minOSVersion();

            // dyld is always built for the current OS, so we can get the current OS version
            // from the load command in dyld itself.
            uint32_t dyldMinOS = ImageLoaderMachO::minOSVersion((const mach_header*)&__dso_handle);
            if ( mainMinOS > dyldMinOS ) {
    #if TARGET_OS_WATCH
                throwf("app was built for watchOS %d.%d which is newer than this simulator %d.%d",
                        mainMinOS >> 16, ((mainMinOS >> 8) & 0xFF),
                        dyldMinOS >> 16, ((dyldMinOS >> 8) & 0xFF));
    #elif TARGET_OS_TV
                throwf("app was built for tvOS %d.%d which is newer than this simulator %d.%d",
                        mainMinOS >> 16, ((mainMinOS >> 8) & 0xFF),
                        dyldMinOS >> 16, ((dyldMinOS >> 8) & 0xFF));
    #else
                throwf("app was built for iOS %d.%d which is newer than this simulator %d.%d",
                        mainMinOS >> 16, ((mainMinOS >> 8) & 0xFF),
                        dyldMinOS >> 16, ((dyldMinOS >> 8) & 0xFF));
    #endif
            }
        }
#endif


    #if SUPPORT_ACCELERATE_TABLES
        sAllImages.reserve((sAllCacheImagesProxy != NULL) ? 16 : INITIAL_IMAGE_COUNT);
    #else
        sAllImages.reserve(INITIAL_IMAGE_COUNT);
    #endif

#if defined(__x86_64__) && !TARGET_OS_SIMULATOR
        //设置加载动态库版本
        if (dyld::isTranslated()) {……}
#endif

        // Now that shared cache is loaded, setup an versioned dylib overrides
    #if SUPPORT_VERSIONED_PATHS
        //检查版本路径
        checkVersionedPaths();
    #endif


……

        // load any inserted libraries
        //DYLD_INSERT_LIBRARIES 插入动态库
        if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
            //遍历加载插入动态库
            for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
                loadInsertedDylib(*lib);
        }
        // record count of inserted libraries so that a flat search will look at 
        // inserted libraries, then main, then others.
        sInsertedDylibCount = sAllImages.size()-1;

        // link main executable
        //记录链接主程序
        gLinkContext.linkingMainExecutable = true;
#if SUPPORT_ACCELERATE_TABLES
        if ( mainExcutableAlreadyRebased ) {
            // previous link() on main executable has already adjusted its internal pointers for ASLR
            // work around that by rebasing by inverse amount
            sMainExecutable->rebase(gLinkContext, -mainExecutableSlide);
        }
#endif
        //主程序的链接
        link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
        sMainExecutable->setNeverUnloadRecursive();
        if ( sMainExecutable->forceFlat() ) {
            gLinkContext.bindFlat = true;
            gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
        }

        // link any inserted libraries
        // do this after linking main executable so that any dylibs pulled in by inserted 
        // dylibs (e.g. libSystem) will not be in front of dylibs the program uses
        if ( sInsertedDylibCount > 0 ) {
            for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                //i+1因为主程序,插入的在主程序后面
                ImageLoader* image = sAllImages[i+1];
                //插入动态库的链接
                link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
                image->setNeverUnloadRecursive();
            }
            if ( gLinkContext.allowInterposing ) {
                // only INSERTED libraries can interpose
                // register interposing info after all inserted libraries are bound so chaining works
                for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                    ImageLoader* image = sAllImages[i+1];
                    image->registerInterposing(gLinkContext);
                }
            }
        }

……
    #if SUPPORT_ACCELERATE_TABLES
        //判断条件不满足,持续 goto reloadAllImages
        if ( (sAllCacheImagesProxy != NULL) && ImageLoader::haveInterposingTuples() ) {
            // Accelerator tables cannot be used with implicit interposing, so relaunch with accelerator tables disabled
            ImageLoader::clearInterposingTuples();
            // unmap all loaded dylibs (but not main executable)
            for (long i=1; i < sAllImages.size(); ++i) {
                ImageLoader* image = sAllImages[i];
                if ( image == sMainExecutable )
                    continue;
                if ( image == sAllCacheImagesProxy )
                    continue;
                image->setCanUnload();
                ImageLoader::deleteImage(image);
            }
            // note: we don't need to worry about inserted images because if DYLD_INSERT_LIBRARIES was set we would not be using the accelerator table
            sAllImages.clear();
            sImageRoots.clear();
            sImageFilesNeedingTermination.clear();
            sImageFilesNeedingDOFUnregistration.clear();
            sAddImageCallbacks.clear();
            sRemoveImageCallbacks.clear();
            sAddLoadImageCallbacks.clear();
            sAddBulkLoadImageCallbacks.clear();
            sDisableAcceleratorTables = true;
            sAllCacheImagesProxy = NULL;
            sMappedRangesStart = NULL;
            mainExcutableAlreadyRebased = true;
            gLinkContext.linkingMainExecutable = false;
            resetAllImages();
            goto reloadAllImages;
        }
    #endif

……

        // Bind and notify for the inserted images now interposing has been registered
        if ( sInsertedDylibCount > 0 ) {
            for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                ImageLoader* image = sAllImages[i+1];
                //绑定插入动态库
                image->recursiveBind(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true, nullptr);
            }
        }
        
        // <rdar://problem/12186933> do weak binding only after all inserted images linked
        //弱符号绑定
        sMainExecutable->weakBind(gLinkContext);
        gLinkContext.linkingMainExecutable = false;

        sMainExecutable->recursiveMakeDataReadOnly(gLinkContext);

        CRSetCrashLogMessage("dyld: launch, running initializers");
    #if SUPPORT_OLD_CRT_INITIALIZATION
        // Old way is to run initializers via a callback from crt1.o
        if ( ! gRunInitializersOldWay ) 
            initializeMainExecutable(); 
    #else
        // run all initializers
        //初始化主程序,到目前为止还没有执行到主程序中的代码。
        initializeMainExecutable(); 
    #endif

……
        {
            // find entry point for main executable
            //找到主程序入口 LC_MAIN
            result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
            if ( result != 0 ) {
                // main executable uses LC_MAIN, we need to use helper in libdyld to call into main()
                if ( (gLibSystemHelpers != NULL) && (gLibSystemHelpers->version >= 9) )
                    *startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
                else
                    halt("libdyld.dylib support not present for LC_MAIN");
            }
            else {
                // main executable uses LC_UNIXTHREAD, dyld needs to let "start" in program set up for main()
                result = (uintptr_t)sMainExecutable->getEntryFromLC_UNIXTHREAD();
                *startGlue = 0;
            }
        }
    }
    catch(const char* message) {
        syncAllImages();
        halt(message);
    }
    catch(...) {
        dyld::log("dyld: launch failed\n");
    }

    CRSetCrashLogMessage("dyld2 mode");
#if !TARGET_OS_SIMULATOR
    if (sLogClosureFailure) {
        // We failed to launch in dyld3, but dyld2 can handle it. synthesize a crash report for analytics
        dyld3::syntheticBacktrace("Could not generate launchClosure, falling back to dyld2", true);
    }
#endif

    if (sSkipMain) {
        notifyMonitoringDyldMain();
        if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) {
            dyld3::kdebug_trace_dyld_duration_end(launchTraceID, DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, 0, 0, 2);
        }
        ARIADNEDBG_CODE(220, 1);
        result = (uintptr_t)&fake_main;
        *startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
    }
    //返回主程序
    return result;
}

main函数主要是返回了主程序的函数入口(main`):

  • 配置环境,获取主程序HeaderSlideASLR
  • 加载共享缓存:mapSharedCache。这个时候只读了主程序还没有加载主程序。iOS必须有共享缓存。
    checkSharedRegionDisable方法中说明了iOS必须有共享缓存:
    image.png
  • dyld2/dyld3ClosureMode闭包模式)加载程序:iOS11引入dyld3闭包模式,以回调的方式加载。闭包模式加载速度更快,效率更高。iOS13后动态库和三方库都使ClosureMode加载。
    • dyld3:
      • 使用mainClosure来加载。
      • 找到/创建mainClosure后,通过launchWithClosure启动主程序,启动失败后会有重新创建mainClosure重新启动的逻辑。成功后返回result(主程序入口main)。launchWithClosure中的逻辑和dyld2启动主程序逻辑基本相同。
    • dyld2:启动主程序
      • 实例化主程序instantiateFromLoadedImage
      • 插入&加载动态库 loadInsertedDylib。加载在loadInsertedDylib中调用load(主程序和动态库都会添加到allImagesloadAllImages
      • 链接主程序和链接插入动态库(link,主程序链接在前)。在这个过程中记录了dyld加载的时长。可以通过配置环境变量打印出来。
      • 绑定符号(非懒加载、弱符号),懒加载在调用时绑定。
      • 初始化主程序initializeMainExecutable,这个时候还没有执行到主程序中的代码。
      • 找到主程序入口 LC_MAIN,然后返回主程序。

DYLD_PRINT_OPTSDYLD_PRINT_ENV环境变量配置,可以打印环境变量配置(在"Scheme -> Arguments -> Environment Variables"中配置):

环境变量配置

ASLRimage list0个主程序第一个地址。
ASLR

2.1.1.1mapSharedCache加载共享缓存

共享缓存专门缓存系统动态库,如:UIKitFoundation等。(自己的库、三方库不行)

2.1.1.1.1 mapSharedCache

mapSharedCache真正调用的是loadDyldCache

static void mapSharedCache(uintptr_t mainExecutableSlide)
{
    ……
    //真正调用的是loadDyldCache
    loadDyldCache(opts, &sSharedCacheLoadInfo);
    ……
}
2.1.1.1.2 loadDyldCache
bool loadDyldCache(const SharedCacheOptions& options, SharedCacheLoadInfo* results)
{
    results->loadAddress        = 0;
    results->slide              = 0;
    results->errorMessage       = nullptr;

#if TARGET_OS_SIMULATOR
    // simulator only supports mmap()ing cache privately into process
    return mapCachePrivate(options, results);
#else
    if ( options.forcePrivate ) {
        // mmap cache into this process only
        //仅加载到当前进程
        return mapCachePrivate(options, results);
    }
    else {
        // fast path: when cache is already mapped into shared region
        bool hasError = false;
        //已经加载不进行任何处理
        if ( reuseExistingCache(options, results) ) {
            hasError = (results->errorMessage != nullptr);
        } else {
            // slow path: this is first process to load cache
            //当前进程第一次加载
            hasError = mapCacheSystemWide(options, results);
        }
        return hasError;
    }
#endif
}

loadDyldCache3个逻辑:
1.仅加载到当前进程调用mapCachePrivate。不放入共享缓存,仅自己使用。
2.已经加载过不进行任何处理。
3.当前进程第一次加载调用mapCacheSystemWide

动态库的共享缓存在整个应用的启动过程中是最先被加载的。

2.1.1.2 instantiateFromLoadedImage 实例化主程序(创建image

//header aslr path
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);
    //将主程序加入到all images
        addImage(image);
        return (ImageLoaderMachO*)image;
//  }
    
//  throw "main executable not a known format";
}
  • 传入主程序的HeaderASLRpath实例化主程序生成image
  • image加入all images中。

实际上实例化真正调用的是ImageLoaderMachO::instantiateMainExecutable

// 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;
    //获取Load Commands
    sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd);
    // instantiate concrete class based on content of load commands
    //根据 compressed 确定用哪个子类进行加载,ImageLoader是个抽象类,根据值选择对应的子类实例化主程序
    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
}
  • 调用sniffLoadCommands生成相关信息,比如compressed
  • 根据 compressed 确定用哪个子类进行加载,ImageLoader是个抽象类,根据值选择对应的子类实例化主程序。

sniffLoadCommands

void ImageLoaderMachO::sniffLoadCommands(const macho_header* mh, const char* path, bool inCache, bool* compressed,
                                            unsigned int* segCount, unsigned int* libCount, const LinkContext& context,
                                            const linkedit_data_command** codeSigCmd,
                                            const encryption_info_command** encryptCmd)
{
    //根据LC_DYLIB_INFO 和  LC_DYLD_INFO_ONLY 来获取的
    *compressed = false;
    //segment数量
    *segCount = 0;
    //lib数量
    *libCount = 0;
    //代码签名和加密
    *codeSigCmd = NULL;
    *encryptCmd = NULL;
        ……
    // fSegmentsArrayCount is only 8-bits
    //segCount 最多 256 个
    if ( *segCount > 255 )
        dyld::throwf("malformed mach-o image: more than 255 segments in %s", path);

    // fSegmentsArrayCount is only 8-bits
    //libCount最多 4096 个
    if ( *libCount > 4095 )
        dyld::throwf("malformed mach-o image: more than 4095 dependent libraries in %s", path);

    if ( needsAddedLibSystemDepency(*libCount, mh) )
        *libCount = 1;

    // dylibs that use LC_DYLD_CHAINED_FIXUPS have that load command removed when put in the dyld cache
    if ( !*compressed && (mh->flags & MH_DYLIB_IN_CACHE) )
        *compressed = true;
}
  • compressed是根据LC_DYLIB_INFOLC_DYLD_INFO_ONLY来获取的。
  • segCount最多256个。
  • libCount最多4096个。

2.1.1.3 loadInsertedDylib 插入&加载动态库

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,加载动态库的真正函数
        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));
    }
}
  • 根据上下文初始化配置调用load加载动态库。

2.1.1.4ImageLoader::link链接主程序/动态库

void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath)
{
    //dyld::log("ImageLoader::link(%s) refCount=%d, neverUnload=%d\n", imagePath, fDlopenReferenceCount, fNeverUnload);
    
    // clear error strings
    (*context.setErrorStrings)(0, NULL, NULL, NULL);
    //起始时间。用于记录时间间隔
    uint64_t t0 = mach_absolute_time();
    //递归加载主程序依赖的库,完成之后发通知。
    this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath);
    context.notifyBatch(dyld_image_state_dependents_mapped, preflightOnly);

    // we only do the loading step for preflights
    if ( preflightOnly )
        return;

    uint64_t t1 = mach_absolute_time();
    context.clearAllDepths();
    this->updateDepth(context.imageCount());

    __block uint64_t t2, t3, t4, t5;
    {
        dyld3::ScopedTimer(DBG_DYLD_TIMING_APPLY_FIXUPS, 0, 0, 0);
        t2 = mach_absolute_time();
        //Rebase修正ASLR
        this->recursiveRebaseWithAccounting(context);
        context.notifyBatch(dyld_image_state_rebased, false);

        t3 = mach_absolute_time();
        if ( !context.linkingMainExecutable )
            //绑定NoLazy符号
            this->recursiveBindWithAccounting(context, forceLazysBound, neverUnload);

        t4 = mach_absolute_time();
        if ( !context.linkingMainExecutable )
            //绑定弱符号
            this->weakBind(context);
        t5 = mach_absolute_time();
    }

    // interpose any dynamically loaded images
    if ( !context.linkingMainExecutable && (fgInterposingTuples.size() != 0) ) {
        dyld3::ScopedTimer timer(DBG_DYLD_TIMING_APPLY_INTERPOSING, 0, 0, 0);
        //递归应用插入的动态库
        this->recursiveApplyInterposing(context);
    }

    // now that all fixups are done, make __DATA_CONST segments read-only
    if ( !context.linkingMainExecutable )
        this->recursiveMakeDataReadOnly(context);

    if ( !context.linkingMainExecutable )
        context.notifyBatch(dyld_image_state_bound, false);
    uint64_t t6 = mach_absolute_time();

    if ( context.registerDOFs != NULL ) {
        std::vector<DOFInfo> dofs;
        this->recursiveGetDOFSections(context, dofs);
        //注册
        context.registerDOFs(dofs);
    }
    //计算结束时间.
    uint64_t t7 = mach_absolute_time();

    // clear error strings
    (*context.setErrorStrings)(0, NULL, NULL, NULL);
    //配置环境变量,就可以看到dyld应用加载的时长。
    fgTotalLoadLibrariesTime += t1 - t0;
    fgTotalRebaseTime += t3 - t2;
    fgTotalBindTime += t4 - t3;
    fgTotalWeakBindTime += t5 - t4;
    fgTotalDOF += t7 - t6;
    // done with initial dylib loads
    fgNextPIEDylibAddress = 0;
}
  • 修正ASLR
  • 绑定NoLazy符号。
  • 绑定弱符号。
  • 注册。
  • 记录时间,可以通过配置看到dyld应用加载时长。

2.1.1.5 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 ) {
        //从1开始到最后。(第0个为主程序)
        for(size_t i=1; i < rootCount; ++i) {
            //image初始化,调用 +load 和 构造函数
            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]);
}
  • 初始化images,下标从1开始,然后再初始化主程序(下标0),runInitializers
  • 可以配置环境变量DYLD_PRINT_STATISTICSDYLD_PRINT_STATISTICS_DETAILS打印相信信息。
2.1.1.5.1 dyld ImageLoader::runInitializers(ImageLoader.cpp)
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);
}
  • up.count值设置为1然后调用processInitializers

dyld`ImageLoader::processInitializers

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.
    //这里count为1
    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);
}
  • 最终调用了recursiveInitialization

dyld ImageLoader::recursiveInitialization(ImageLoader.cpp)

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;
            //最终调用到objc里面了加载了所有load方法
            context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
            
            // initialize this image
            //调用c++构造函数
            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();
}
  • 调用notifySingle最终调用到了objc中调用了所有的+ load方法。
  • 调用doInitialization最终调用了c++的系统构造函数。

c++系统构造函数

__attribute__((constructor)) void func() {
   printf("\n ---func--- \n");
}

dyld dyld::notifySingledyld2.cpp

//调用到objc里面去
static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
……

    if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
        uint64_t t0 = mach_absolute_time();
        dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
        //回调指针 sNotifyObjCInit 是在 registerObjCNotifiers 中赋值的。这里执行会跑到objc的load_images中
        (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
        uint64_t t1 = mach_absolute_time();
        uint64_t t2 = mach_absolute_time();
        uint64_t timeInObjC = t1-t0;
        uint64_t emptyTime = (t2-t1)*100;
        if ( (timeInObjC > emptyTime) && (timingInfo != NULL) ) {
            timingInfo->addTime(image->getShortName(), timeInObjC);
        }
    }
……
}
  • notifySingle中找不到load image的调用(从堆栈信息中可以看到notifySingle之后是load image)。
  • 这个函数执行一个回调sNotifyObjCInit

搜索下回调sNotifyObjCInit的赋值操作,发现是在registerObjCNotifiers中赋值的
registerObjCNotifiers

//那么谁调用的 registerObjCNotifiers ? _dyld_objc_notify_register
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
    // record functions to call
    sNotifyObjCMapped   = mapped;
    //赋值 sNotifyObjCInit,传进来的参数。
    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());
        }
    }
}
  • registerObjCNotifiers赋值来自于第二个参数_dyld_objc_notify_init
  • 搜索发现是_dyld_objc_notify_register调用的registerObjCNotifiers

** _dyld_objc_notify_register**(dyldAPIs.cpp

// _dyld_objc_notify_register 调用 registerObjCNotifiers
//这里找不到 _dyld_objc_notify_register 调用者。打符号断点查看被objc-os.mm中 _objc_init 调用
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);
}
  • _dyld_objc_notify_register的调用者找不到。

打符号断点_dyld_objc_notify_register排查调用情况:

image.png

可以看到是被_objc_init调用的。
_objc_init的调用在objc-os.mm中,查看源码:

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();
#if __OBJC2__
    cache_t::init();
#endif
    _imp_implementationWithBlock_init();

    //_objc_init 调用dyldAPIs.cpp 中_dyld_objc_notify_register,第二个参数是load_images
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}
  • 证实是_objc_init调用了_dyld_objc_notify_register
  • 第二个参数是load_images

load_imagesobjc-runtime-new.mm

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
    call_load_methods();
}
  • 最终调用了call_load_methods

call_load_methods (objc-loadmethod.mm)

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();
    
    //循环调用 call_class_loads,类的load方法在这一刻被调用
    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            //调用每个类的load
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        //调用分类load,这里也就说明分类的 load 在所有类的load方法调用后才调用。(针对image而言)
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}
  • 调用call_class_loads加载类的+ load
  • 接着调用call_category_loads加载分类的+ load。这里也就说明分类的 load在所有类的load方法调用后才调用。(针对image而言)。

在这里也就调用到了+ load方法,这也就是+ loadmain之前被调用的原因。

ImageLoaderMachO::doInitialization(ImageLoaderMachO.cpp)

bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
    CRSetCrashLogMessage2(this->getPath());

    // mach-o has -init and static initializers
    doImageInit(context);
    //加载c++构造函数
    doModInitFunctions(context);
    
    CRSetCrashLogMessage2(NULL);
    
    return (fHasDashInit || fHasInitializers);
}

加上以下代码查看MachO文件:

__attribute__((constructor)) void func1() {
    printf("\n ---func1--- \n");
}

__attribute__((constructor)) void func2() {
    printf("\n ---func2--- \n");
}

会发现MachO中多了__mod_init_func

image.png

  • 调用doModInitFunctions函数加载c++构造函数(__attribute__((constructor))修饰的c函数)

根据以上分析可以看到dyld是按image list顺序从第1image调用runInitializers(可以看做是以image分组)。再调用下一个imagerunInitializers最后再调用主程序(下标为0)的runInitializers。在runInitializers内部先调用所有类的+load,再调用所有分类的+ load,最后调用c++的构造函数。
objc中调用loaddyld中调用doModInitFunctions
⚠️如果在+ load中做了防护,那么我们可以通过在+ load执行前断住外部符号做处理。这样就可以绕过防护了。
防护最重要的就是不让别人找到防护的逻辑,只要能找到那么破解就很容易了。
案例分析:你真的了解dyld么?

2.2整体流程

dyld加载流程

总结

总结
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容