dyld源碼分析

211623315263_.pic.jpg

一瞧毙、dyld簡介

dyld( the dynamic link editor 動(dòng)態(tài)鏈接器)饶号,是蘋果操作系統(tǒng)的一個(gè)重要的組成部分纯趋。在iOS/Mac OSX系統(tǒng)中俏蛮,僅有很少量的進(jìn)程只需要內(nèi)核就能完成加載桐猬,基本上所有的進(jìn)程都是動(dòng)態(tài)鏈接的麦撵,所以Mach-O鏡像文件中會(huì)有很多對(duì)外部的庫和符號(hào)的引用,但是這些引用并不能直接用溃肪,在啟動(dòng)時(shí)還必須要通過這些引用進(jìn)行內(nèi)容的填補(bǔ)厦坛,這個(gè)填補(bǔ)工作就是由動(dòng)態(tài)鏈接器dyld來完成的,也就是符號(hào)綁定乍惊。

下載源碼

二杜秸、dyld流程分析

dyld的主要作用是加載Mach-O鏡像文件,鏈接外部庫和符號(hào)綁定润绎。所有想要查看其內(nèi)部方法執(zhí)行順序撬碟,需要在main函數(shù)執(zhí)行前去分析。
那在load方法內(nèi)添加斷點(diǎn)莉撇,查看調(diào)用棧信息呢蛤。


截屏2021-06-03 上午11.41.23.png

1、_dyld_start分析

通過棧信息發(fā)現(xiàn)最早執(zhí)行的函數(shù)就是_dyld_start,進(jìn)入_dyld_start查看匯編執(zhí)行順序


截屏2021-06-03 下午1.22.59.png

在dyld源碼中全局搜索_dyld_start方法棍郎, 該方法做了底層環(huán)境區(qū)分其障,arm64的源碼如下

#if __arm64__
    .text
    .align 2
    .globl __dyld_start
__dyld_start:
    mov     x28, sp
    and     sp, x28, #~15       // force 16-byte alignment of stack
    mov x0, #0
    mov x1, #0
    stp x1, x0, [sp, #-16]! // make aligned terminating frame
    mov fp, sp          // set up fp to point to terminating frame
    sub sp, sp, #16             // make room for local variables
#if __LP64__
    ldr     x0, [x28]               // get app's mh into x0
    ldr     x1, [x28, #8]           // get argc into x1 (kernel passes 32-bit int argc as 64-bits on stack to keep alignment)
    add     x2, x28, #16            // get argv into x2
#else
    ldr     w0, [x28]               // get app's mh into x0
    ldr     w1, [x28, #4]           // get argc into x1 (kernel passes 32-bit int argc as 64-bits on stack to keep alignment)
    add     w2, w28, #8             // get argv into x2
#endif
    adrp    x3,___dso_handle@page
    add     x3,x3,___dso_handle@pageoff // get dyld's mh in to x4
    mov x4,sp                   // x5 has &startGlue

    // call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
    bl  __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
    mov x16,x0                  // save entry point address in x16
#if __LP64__
    ldr     x1, [sp]
#else
    ldr     w1, [sp]
#endif
    cmp x1, #0
    b.ne    Lnew

    // LC_UNIXTHREAD way, clean up stack and jump to result
#if __LP64__
    add sp, x28, #8             // restore unaligned stack pointer without app mh
#else
    add sp, x28, #4             // restore unaligned stack pointer without app mh
#endif
#if __arm64e__
    braaz   x16                     // jump to the program's entry point
#else
    br      x16                     // jump to the program's entry point
#endif

    // LC_MAIN case, set up stack for call to main()
Lnew:   mov lr, x1          // simulate return address into _start in libdyld.dylib
#if __LP64__
    ldr x0, [x28, #8]       // main param1 = argc
    add x1, x28, #16        // main param2 = argv
    add x2, x1, x0, lsl #3
    add x2, x2, #8          // main param3 = &env[0]
    mov x3, x2
Lapple: ldr x4, [x3]
    add x3, x3, #8
#else
    ldr w0, [x28, #4]       // main param1 = argc
    add x1, x28, #8         // main param2 = argv
    add x2, x1, x0, lsl #2
    add x2, x2, #4          // main param3 = &env[0]
    mov x3, x2
Lapple: ldr w4, [x3]
    add x3, x3, #4
#endif
    cmp x4, #0
    b.ne    Lapple          // main param4 = apple
#if __arm64e__
    braaz   x16
#else
    br      x16
#endif

#endif // __arm64__

2、dyldbootstrap::start分析

_dyld_start匯編內(nèi)調(diào)用dyldbootstrap::start涂佃,根據(jù)dyldbootstrap找到方法start的實(shí)現(xiàn)

//
//  This is code to bootstrap dyld.  This work in normally done for a program by dyld and crt.
//  In dyld we have to do this manually.
//
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);
}

解釋:

  • 調(diào)用rebaseDyld() 修正Mach-O文件指針(基地址復(fù)位)
  • __guard_setup 棧溢出保護(hù)
  • 調(diào)用dyld::_main
2.1 rebaseDyld分析

源碼實(shí)現(xiàn)

//
// On disk, all pointers in dyld's DATA segment are chained together.
// They need to be fixed up to be real pointers to run.
//
static void rebaseDyld(const dyld3::MachOLoaded* dyldMH)
{
    // walk all fixups chains and rebase dyld
    const dyld3::MachOAnalyzer* ma = (dyld3::MachOAnalyzer*)dyldMH;
    assert(ma->hasChainedFixups());
    uintptr_t slide = (long)ma; // all fixup chain based images have a base address of zero, so slide == load address
    __block Diagnostics diag;
    ma->withChainStarts(diag, 0, ^(const dyld_chained_starts_in_image* starts) {
        ma->fixupAllChainedFixups(diag, starts, slide, dyld3::Array<const void*>(), nullptr);
    });
    diag.assertNoError();

    // now that rebasing done, initialize mach/syscall layer
    mach_init();

    // <rdar://47805386> mark __DATA_CONST segment in dyld as read-only (once fixups are done)
    ma->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& info, bool& stop) {
        if ( info.readOnlyData ) {
            ::mprotect(((uint8_t*)(dyldMH))+info.vmAddr, (size_t)info.vmSize, VM_PROT_READ);
        }
    });
}

解釋:
Mach-O 文件都是固定不變的励翼,但是每一次運(yùn)行同一個(gè)方法的指針地址都不同,地址空間布局隨機(jī)化處理(Address Space Layout Randomization辜荠,簡稱 ASLR)是為了更安全汽抚,增加分析應(yīng)用代碼成本。

dyld重定位元數(shù)據(jù)中的指針是比較耗時(shí)操作伯病,在應(yīng)用每次啟動(dòng)都會(huì)執(zhí)行操作造烁。如果可執(zhí)行文件太大或者指針類型太多都會(huì)導(dǎo)致指針重定位耗時(shí)增加,所以在這個(gè)環(huán)節(jié)我們是可以做一些優(yōu)化午笛,也應(yīng)該在平時(shí)開發(fā)過程注意代碼的規(guī)范減少這些環(huán)境的耗時(shí)惭蟋。
dyld重定位分析

ASLRCodeSign

ASLR:是Address Space Layout Randomization(地址空間布局隨機(jī)化)的簡稱苟翻。App在被啟動(dòng)的時(shí)候瞎暑,程序會(huì)被映射到邏輯地址空間拳昌,這個(gè)邏輯地址空間有一個(gè)起始地址肖爵,ASLR技術(shù)讓這個(gè)起始地址是隨機(jī)的。這個(gè)地址如果是固定的赘被,黑客很容易就用起始地址+函數(shù)偏移地址找到對(duì)應(yīng)的函數(shù)地址碾篡。

Code Sign:就是蘋果代碼加密簽名機(jī)制砍的,但是在Code Sign操作的時(shí)候驼卖,加密的哈希不是針對(duì)整個(gè)文件氨肌,而是針對(duì)每一個(gè)Page的。這個(gè)就保證了dyld在加載的時(shí)候酌畜,可以對(duì)每個(gè)page進(jìn)行獨(dú)立的驗(yàn)證怎囚。

3、dyld::_main分析

dyld::_main源碼太長桥胞,根據(jù)下面9個(gè)步驟拆解分析

3.1 環(huán)境變量配置

根據(jù)環(huán)境變量設(shè)置相應(yīng)的值恳守,獲取當(dāng)前運(yùn)行的架構(gòu)信息,判斷dyld版本做處理
dyld3和dyld2的差異

    //Check and see if there are any kernel flags (檢查是否有任何內(nèi)核標(biāo)志)
    dyld3::BootArgs::setFlags(hexToUInt64(_simple_getenv(apple, "dyld_flags"), nullptr));
    
    // Grab the cdHash of the main executable from the environment (從環(huán)境中獲取主可執(zhí)行文件)
    uint8_t mainExecutableCDHashBuffer[20];
    const uint8_t* mainExecutableCDHash = nullptr;
    if ( hexToBytes(_simple_getenv(apple, "executable_cdhash"), 40, mainExecutableCDHashBuffer) )
        mainExecutableCDHash = mainExecutableCDHashBuffer;
    
    // Set the platform ID in the all image infos so debuggers can tell the process type (在鏡像信息中設(shè)置平臺(tái)贩虾,這樣調(diào)試器就可以告訴進(jìn)程類型)
    if (gProcessInfo->version >= 16) {
        __block bool platformFound = false;
        ((dyld3::MachOFile*)mainExecutableMH)->forEachSupportedPlatform(^(dyld3::Platform platform, uint32_t minOS, uint32_t sdk) {
            if (platformFound) {
                halt("MH_EXECUTE binaries may only specify one platform");
            }
            gProcessInfo->platform = (uint32_t)platform;
            platformFound = true;
        });
    }
    
    CRSetCrashLogMessage("dyld: launch started");
    
    setContext(mainExecutableMH, argc, argv, envp, apple);
    
    // Pickup the pointer to the exec path. (提取指向exec路徑)
    sExecPath = _simple_getenv(apple, "executable_path");
    
    // <rdar://problem/13868260> Remove interim apple[0] transition code from dyld
    if (!sExecPath) sExecPath = apple[0];
    
#if __IPHONE_OS_VERSION_MIN_REQUIRED && !TARGET_OS_SIMULATOR
    // <rdar://54095622> kernel is not passing a real path for main executable (更新exec的全路徑)
    if ( strncmp(sExecPath, "/var/containers/Bundle/Application/", 35) == 0 ) {
        if ( char* newPath = (char*)malloc(strlen(sExecPath)+10) ) {
            strcpy(newPath, "/private");
            strcat(newPath, sExecPath);
            sExecPath = newPath;
        }
    }
#endif
    
    if ( sExecPath[0] != '/' ) {
        // have relative path, use cwd to make absolute
        char cwdbuff[MAXPATHLEN];
        if ( getcwd(cwdbuff, MAXPATHLEN) != NULL ) {
            // maybe use static buffer to avoid calling malloc so early...
            char* s = new char[strlen(cwdbuff) + strlen(sExecPath) + 2];
            strcpy(s, cwdbuff);
            strcat(s, "/");
            strcat(s, sExecPath);
            sExecPath = s;
        }
    }
    
    // 檢查環(huán)境變量 設(shè)置環(huán)境變量
    checkEnvironmentVariables(envp);
    // 設(shè)置回退路徑
    defaultUninitializedFallbackPaths(envp);
    
    // 如果設(shè)置DYLD_PRINT_OPTS,打印參數(shù)
    if ( sEnv.DYLD_PRINT_OPTS )
        printOptions(argv);
    // 如果設(shè)置DYLD_PRINT_ENV,打印環(huán)境變量
    if ( sEnv.DYLD_PRINT_ENV )
        printEnvironmentVariables(envp);

    // 設(shè)置臨時(shí)路徑
    const char* tempDir = getTempDir(envp);
    if ( (tempDir != nullptr) && (geteuid() != 0) ) {
        // Use realpath to prevent something like TMPRIR=/tmp/../usr/bin
        char realPath[PATH_MAX];
        if ( realpath(tempDir, realPath) != NULL )
            tempDir = realPath;
        if (strncmp(tempDir, "/private/var/mobile/Containers/", strlen("/private/var/mobile/Containers/")) == 0) {
            sJustBuildClosure = true;
        }
    }
    // dyld3 設(shè)置啟動(dòng)閉包模式
    if ( sJustBuildClosure )
        sClosureMode = ClosureMode::On;
    // 獲取當(dāng)前運(yùn)行環(huán)境的架構(gòu)信息
    getHostInfo(mainExecutableMH, mainExecutableSlide);
3.2 共享緩存

檢查是否開啟了共享緩存催烘,創(chuàng)建啟動(dòng)閉包,加載共享緩存缎罢。

    // load shared cache  檢查共享緩存是否開啟
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
    if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
        // 設(shè)置共享緩存配置參數(shù) 
        mapSharedCache();
    }
    
    /*
     enum class ClosureMode {
        // Unset means we haven't provided an env variable or boot-arg to explicitly choose a mode
        Unset,
        // On means we set DYLD_USE_CLOSURES=1, or we didn't have DYLD_USE_CLOSURES=0 but did have
        // -force_dyld3=1 env variable or a customer cache on iOS
        On,
        // Off means we set DYLD_USE_CLOSURES=0, or we didn't have DYLD_USE_CLOSURES=1 but did have
        // -force_dyld2=1 env variable or an internal cache on iOS
        Off,
        // PreBuiltOnly means only use a shared cache closure and don't try build a new one
        PreBuiltOnly
     };
     */
    
    // If we haven't got a closure mode yet, then check the environment and cache type  檢查環(huán)境和緩存類型
    if ( sClosureMode == ClosureMode::Unset ) {
        // First test to see if we forced in dyld2 via a kernel boot-arg
        if ( dyld3::BootArgs::forceDyld2() ) {
            sClosureMode = ClosureMode::Off;
        } else if ( inDenyList(sExecPath) ) {
            sClosureMode = ClosureMode::Off;
        } else if ( sEnv.hasOverride ) {
            sClosureMode = ClosureMode::Off;
        } else if ( dyld3::BootArgs::forceDyld3() ) {
            sClosureMode = ClosureMode::On;
        } else {
            sClosureMode = getPlatformDefaultClosureMode();
        }
    }

#if !TARGET_OS_SIMULATOR
    if ( sClosureMode == ClosureMode::Off ) {
        if ( gLinkContext.verboseWarnings )
            dyld::log("dyld: not using closure because of DYLD_USE_CLOSURES or -force_dyld2=1 override\n");
    } else {
        const dyld3::closure::LaunchClosure* mainClosure = nullptr;
        dyld3::closure::LoadedFileInfo mainFileInfo;
        mainFileInfo.fileContent = mainExecutableMH;
        mainFileInfo.path = sExecPath;
        // FIXME: If we are saving this closure, this slice offset/length is probably wrong in the case of FAT files.
        mainFileInfo.sliceOffset = 0;
        mainFileInfo.sliceLen = -1;
        struct stat mainExeStatBuf;
        if ( ::stat(sExecPath, &mainExeStatBuf) == 0 ) {
            mainFileInfo.inode = mainExeStatBuf.st_ino;
            mainFileInfo.mtime = mainExeStatBuf.st_mtime;
        }
        // check for closure in cache first (首先檢查共享緩存是否存在)
        if ( sSharedCacheLoadInfo.loadAddress != nullptr ) {
                        // 根據(jù)可執(zhí)行文件路徑取出共享緩存包
            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());
        }

        // We only want to try build a closure at runtime if its an iOS third party binary, or a macOS binary from the shared cache (運(yùn)行時(shí)構(gòu)建閉包)
        bool allowClosureRebuilds = false;
        if ( sClosureMode == ClosureMode::On ) {
            allowClosureRebuilds = true;
        } else if ( (sClosureMode == ClosureMode::PreBuiltOnly) && (mainClosure != nullptr) ) {
            allowClosureRebuilds = true;
        }

        if ( (mainClosure != nullptr) && !closureValid(mainClosure, mainFileInfo, mainExecutableCDHash, true, envp) )
            mainClosure = nullptr;

        // If we didn't find a valid cache closure then try build a new one (沒有找到一個(gè)有效的緩存包伊群,那么嘗試構(gòu)建一個(gè)新的)
        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);
            if ( mainClosure == nullptr ) {
                // if  no cached closure found, build new one
                mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp);
            }
        }

        // exit dyld after closure is built, without running program
        // (關(guān)閉后退出dyld,不運(yùn)行程序)
        if ( sJustBuildClosure )
            _exit(EXIT_SUCCESS);

        // try using launch closure
        if ( mainClosure != nullptr ) {
            CRSetCrashLogMessage("dyld3: launch started");
            bool launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
                                              mainExecutableSlide, argc, argv, envp, apple, &result, startGlue);
            if ( !launched && allowClosureRebuilds ) {
                // closure is out of date, build new one
                mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp);
                if ( mainClosure != nullptr ) {
                    launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
                                                 mainExecutableSlide, argc, argv, envp, apple, &result, startGlue);
                }
            }
            if ( launched ) {
#if __has_feature(ptrauth_calls)
                // start() calls the result pointer as a function pointer so we need to sign it.
                result = (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)result, 0, 0);
#endif
                if (sSkipMain)
                    result = (uintptr_t)&fake_main;
                return result;
            }
            else {
                if ( gLinkContext.verboseWarnings ) {
                    dyld::log("dyld: unable to use closure %p\n", mainClosure);
                }
            }
        }
    }

在launchWithClosure方法內(nèi)部策精,根據(jù)已知的共享緩存包舰始, 取出共享緩存的所有鏡像,取出鏡像的方法表咽袜,記錄加載的鏡像丸卷。初始化allImage,allImage添加鏡像询刹。


截屏2021-06-08 上午11.10.35.png
3.3 主程序初始化(imageLoader)

調(diào)用instantiateFromLoadedImage函數(shù)實(shí)例化了一個(gè)ImageLoader對(duì)象

        // instantiate ImageLoader for main executable (為可執(zhí)行文件實(shí)例化ImageLoader)
        sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
        gLinkContext.mainExecutable = sMainExecutable;
        gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);
3.4 插入動(dòng)態(tài)庫

遍歷DYLD_INSERT_LIBRARIES環(huán)境變量谜嫉,調(diào)用loadInsertedDylib加載

截屏2021-06-08 下午2.01.40.png

3.5 鏈接主程序
截屏2021-06-08 下午2.29.54.png
3.6 鏈接動(dòng)態(tài)庫

鏈接動(dòng)態(tài)庫在鏈接主程序之后,以便將所有動(dòng)態(tài)庫都能被插入


截屏2021-06-08 下午2.39.24.png
3.7 符號(hào)綁定

1凹联、如果對(duì)應(yīng)地址在共享緩存中骄恶,找到該鏡像的符號(hào)綁定表直接使用。

截屏2021-06-08 下午2.57.20.png

2匕垫、主程序符號(hào)綁定僧鲁,先綁定引用的庫,再綁定鏡像文件
截屏2021-06-08 下午3.03.34.png

3象泵、綁定已插入鏡像
截屏2021-06-08 下午3.32.48.png

4寞秃、符號(hào)綁定方法
截屏2021-06-08 下午3.08.44.png
符號(hào)綁定

3.8 執(zhí)行初始化方法

截屏2021-06-08 下午3.40.04.png

截屏2021-06-08 下午3.47.37.png

查找runInitializers方法實(shí)現(xiàn),在ImageLoader文件內(nèi)找到其實(shí)現(xiàn)
截屏2021-06-08 下午3.59.02.png

初始化主要執(zhí)行的方法為processInitializers
截屏2021-06-08 下午4.00.20.png

截屏2021-06-08 下午4.23.46.png

各個(gè)鏡像初始化時(shí)先找到初始化方法偶惠,判斷方法是否實(shí)現(xiàn)春寿,執(zhí)行各個(gè)鏡像的初始化方法。
截屏2021-06-08 下午4.13.17.png

3.9 尋找主程序入口(main函數(shù))
截屏2021-06-08 下午4.35.32.png

根據(jù)Mach-O文件查找main函數(shù)入口地址
截屏2021-06-08 下午4.37.24.png
截屏2021-06-09 下午6.29.57.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末忽孽,一起剝皮案震驚了整個(gè)濱河市绑改,隨后出現(xiàn)的幾起案子谢床,更是在濱河造成了極大的恐慌,老刑警劉巖厘线,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件识腿,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡造壮,警方通過查閱死者的電腦和手機(jī)渡讼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耳璧,“玉大人成箫,你說我怎么就攤上這事≈伎荩” “怎么了蹬昌?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長攀隔。 經(jīng)常有香客問我凳厢,道長,這世上最難降的妖魔是什么竞慢? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任先紫,我火速辦了婚禮,結(jié)果婚禮上筹煮,老公的妹妹穿的比我還像新娘遮精。我一直安慰自己,他們只是感情好败潦,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布本冲。 她就那樣靜靜地躺著,像睡著了一般劫扒。 火紅的嫁衣襯著肌膚如雪檬洞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天沟饥,我揣著相機(jī)與錄音添怔,去河邊找鬼。 笑死贤旷,一個(gè)胖子當(dāng)著我的面吹牛广料,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播幼驶,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼艾杏,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了盅藻?” 一聲冷哼從身側(cè)響起购桑,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤畅铭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后勃蜘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體硕噩,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年元旬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了榴徐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片守问。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡匀归,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出耗帕,到底是詐尸還是另有隱情穆端,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布仿便,位于F島的核電站体啰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏嗽仪。R本人自食惡果不足惜荒勇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望闻坚。 院中可真熱鬧沽翔,春花似錦、人聲如沸窿凤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雳殊。三九已至橘沥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間夯秃,已是汗流浹背座咆。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留仓洼,地道東北人箫措。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像衬潦,于是被迫代替她去往敵國和親斤蔓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

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

  • 先來看一下iOS/Mac OS系統(tǒng)內(nèi)核架構(gòu) 需要注意:dyld是運(yùn)行在用戶態(tài)的進(jìn)程(下面解釋)友驮。也就是說:App啟...
    溪浣雙鯉閱讀 1,949評(píng)論 2 9
  • 前言: 動(dòng)態(tài)庫和靜態(tài)庫 ? 我們都知道,一段程序的運(yùn)行驾锰,都會(huì)依賴各種各樣的庫卸留,那么什么是程序依賴的庫呢?簡而言之椭豫,...
    xxxxxxxx_123閱讀 458評(píng)論 0 0
  • dyld 全稱是 the dynamic link editor耻瑟。他是蘋果的動(dòng)態(tài)鏈接器,是蘋果操作系統(tǒng)一個(gè)重要的組...
    正_文閱讀 2,324評(píng)論 0 4
  • 本文的目的主要是分析dyld的加載流程赏酥。 1喳整、知識(shí)補(bǔ)充 1.1 編譯過程 其中編譯過程如下圖所示,主要分為以下幾步...
    AcmenL閱讀 683評(píng)論 0 5
  • 前言 之前裸扶,我們研究了很多關(guān)于iOS底層相對(duì)零碎的知識(shí)框都。而iOS對(duì)用戶來說,最重要的就是每一個(gè)APP呵晨。今天魏保,我們來...
    iOS小木偶閱讀 787評(píng)論 1 2