通過dyld源碼中狂,詳細(xì)分析應(yīng)用加載

在我們初學(xué)iOS的時候,分析一個程序的執(zhí)行流程都是從main函數(shù)開始的考传。但是在main函數(shù)之前其實(shí)也做了不少操作吃型,值得我們分析一下。

我們知道一個類的load的方法是先于main函數(shù)執(zhí)行的僚楞,通過對load方法設(shè)置一個斷點(diǎn)勤晚,查看調(diào)用椨诟觯可知程序在加載過程中大致所執(zhí)行的一些方法。

其中可見dyld(the dynamic link editor)蛤袒,它是蘋果的動態(tài)鏈接器乘粒,是蘋果操作系統(tǒng)一個重要組成部分,在系統(tǒng)內(nèi)核做好程序準(zhǔn)備工作之后挺邀,交由dyld負(fù)責(zé)余下的工作揉忘。通過分析dyld源碼,我們來分析dyld做了什么。

準(zhǔn)備分析

1端铛、通過分析_dyld_start的匯編實(shí)現(xiàn)泣矛。發(fā)現(xiàn)調(diào)用了dyldbootstrap::start方法。

# call dyldbootstrap::start(app_mh, argc, argv, slide, dyld_mh, &startGlue)
    movl    8(%rbp),%esi    # param2 = argc into %esi
    leaq    16(%rbp),%rdx   # param3 = &argv[0] into %rdx
    movq    __dyld_start_static(%rip), %r8
    leaq    __dyld_start(%rip), %rcx
    subq     %r8, %rcx  # param4 = slide into %rcx
    leaq    ___dso_handle(%rip),%r8 # param5 = dyldsMachHeader
    leaq    -8(%rbp),%r9

2禾蚕、在下載好的dyld源碼中搜索dyldbootstrap,在這個命名空間中尋找start方法您朽。
在這個方法中,通過slideOfMainExecutable得到因ASLR產(chǎn)生的偏移换淆。通過rebaseDyld重綁定哗总。通過__guard_setup來?xiàng)R绯霰Wo(hù)。

uintptr_t start(const struct macho_header* appsMachHeader, int argc, const char* argv[], 
                intptr_t slide, const struct macho_header* dyldsMachHeader,
                uintptr_t* startGlue)

這個start的方法的返回值是調(diào)用了一個main函數(shù),將start的一些值作為參數(shù)傳到main倍试。

return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);

3讯屈、dyld也可以看做一個程序的執(zhí)行,它的main函數(shù)和我們?nèi)粘i_發(fā)應(yīng)用的main函數(shù)類似县习,都可以看做程序的入口涮母。接下來我們主要便是分析main函數(shù)的實(shí)現(xiàn)。

_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)

加載過程

0x01 配置環(huán)境准颓,設(shè)置環(huán)境變量等

  • 設(shè)置上下文
setContext(mainExecutableMH, argc, argv, envp, apple);
  • 配置進(jìn)程是否受限
 configureProcessRestrictions(mainExecutableMH);
  • 檢查環(huán)境變量
checkEnvironmentVariables(envp);
  • 根據(jù)Xcode設(shè)置的環(huán)境變量哈蝇,來打印程序相應(yīng)參數(shù)。
if ( sEnv.DYLD_PRINT_OPTS )
        printOptions(argv);
if ( sEnv.DYLD_PRINT_ENV ) 
        printEnvironmentVariables(envp);

會打印程序相關(guān)的目錄攘已、用戶級別炮赦、插入的動態(tài)庫、動態(tài)庫的路徑等样勃。

opt[0] = "/var/containers/Bundle/Application/731D64D1-8B04-491B-A512-4010011413E6/dyld.app/dyld"
CA_DEBUG_TRANSACTIONS=0
TMPDIR=/private/var/mobile/Containers/Data/Application/EF8F63AD-B59A-42E7-92EE-076BC9F664D0/tmp
__CF_USER_TEXT_ENCODING=0x1F5:0:0
SHELL=/bin/sh
SQLITE_ENABLE_THREAD_ASSERTIONS=1
OS_ACTIVITY_DT_MODE=YES
HOME=/private/var/mobile/Containers/Data/Application/EF8F63AD-B59A-42E7-92EE-076BC9F664D0
DYLD_PRINT_TO_STDERR=YES
CFFIXED_USER_HOME=/private/var/mobile/Containers/Data/Application/EF8F63AD-B59A-42E7-92EE-076BC9F664D0
NSUnbufferedIO=YES
PATH=/usr/bin:/bin:/usr/sbin:/sbin
LOGNAME=mobile
XPC_SERVICE_NAME=UIKitApplication:dyoung.dyld[0x1b53][62]
DYLD_INSERT_LIBRARIES=/Developer/usr/lib/libBacktraceRecording.dylib:/Developer/usr/lib/libMainThreadChecker.dylib:/Developer/Library/PrivateFrameworks/DTDDISupport.framework/libViewDebuggerSupport.dylib
CLASSIC=0
DYLD_PRINT_OPTS=1
DYLD_PRINT_ENV=1
USER=mobile
XPC_FLAGS=0x1
CA_ASSERT_MAIN_THREAD_TRANSACTIONS=0
DYLD_LIBRARY_PATH=/usr/lib/system/introspection
  • 通過getHostInfo獲取machO頭部獲取當(dāng)前運(yùn)行架構(gòu)的信息吠勘。
static void getHostInfo(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide)

0x02 加載共享緩存庫。

  • 判斷共享緩存庫是否被禁用峡眶。iOS cannot run without shared region剧防,注釋說明iOS平臺下是不能被禁用的。
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
  • 通過mapSharedCache()函數(shù)加載辫樱、進(jìn)入函數(shù)內(nèi)部其主要實(shí)現(xiàn)是loadDyldCache這個函數(shù)峭拘。其中作了如下三種判斷
 if ( options.forcePrivate ) {
        // mmap cache into this process only
        //只加載到當(dāng)前緩存。
        return mapCachePrivate(options, results);
    }
 else {
        // fast path: when cache is already mapped into shared region
        //快速路徑,如果已經(jīng)加載的話就不處理了鸡挠。
        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;
}

0x03 實(shí)例化主程序(Mach0,程序的可執(zhí)行文件)

  • 實(shí)例化過程:instantiateFromLoadedImage
static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)
{
    // try mach-o loader
    //isCompatibleMachO 是檢查mach-o的subtype是否是當(dāng)前cpu可以支持
    if ( isCompatibleMachO((const uint8_t*)mh, path) ) {
        ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
        addImage(image);//將image添加到imagelist拣展。所以我們在Xcode使用image list命令查看的第一個便是我們的machO
        return (ImageLoaderMachO*)image;
    }
    
    throw "main executable not a known format";
}

0x04 加載插入庫

  • 通過loadInsertedDylib方法執(zhí)行插入動態(tài)庫的加載彭沼。在實(shí)現(xiàn)中調(diào)用load方法返回imageLoader對象,

imageLoader是一個抽象基類备埃,專門用于輔助加載特定可執(zhí)行文件格式的類姓惑,對于程序中需要的依賴庫、插入庫按脚,會創(chuàng)建一個對應(yīng)的image對象于毙,對這些image進(jìn)行鏈接,調(diào)用各image的初始化方法等等辅搬,包括對runtime的初始化望众。

// load any inserted libraries
        if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
//遍歷DYLD_INSERT_LIBRARIES的環(huán)境變量。
            for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
                loadInsertedDylib(*lib);
        }

0x05 鏈接主程序伞辛,并加載系統(tǒng)和第三方的動態(tài)庫

  • main中通過link鏈接主程序。
//main 函數(shù)中
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
  • 內(nèi)部通過imageLoader的實(shí)例對象去調(diào)用link方法夯缺。
//image調(diào)用link
image->link(gLinkContext, forceLazysBound, false, neverUnload, loaderRPaths, path);
  • 遞歸加載我們所需要的依賴的系統(tǒng)庫和第三方庫蚤氏。
this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath);
  • 對依賴庫進(jìn)行重定位。相當(dāng)于加上ASLR滑塊踊兜。
this->recursiveRebase(context);
  • 遞歸綁定符號表和弱綁定竿滨。

綁定就是將這個二進(jìn)制調(diào)用的外部符號進(jìn)行綁定的過程。
比如我們objc代碼中需要使用到NSObject, 即符號OBJC_CLASS$_NSObject捏境,但是這個符號又不在我們的二進(jìn)制中于游,在系統(tǒng)庫 Foundation.framework中,因此就需要binding這個操作將對應(yīng)關(guān)系綁定到一起垫言。
lazyBinding就是在加載動態(tài)庫的時候不會立即binding, 當(dāng)?shù)谝淮握{(diào)用這個方法的時候再實(shí)施binding贰剥。 做到的方法也很簡單: 通過dyld_stub_binder 這個符號來做。
lazy binding的方法第一次會調(diào)用到dyld_stub_binder, 然后dyld_stub_binder負(fù)責(zé)找到真實(shí)的方法筷频,并且將地址bind到樁上蚌成,下一次就不用再bind了。

this->recursiveBindWithAccounting(context, forceLazysBound, neverUnload);
this->weakBind(context);
  • 插入動態(tài)庫凛捏。
        if ( sInsertedDylibCount > 0 ) {//有的話就開始鏈接加載担忧。
            for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                ImageLoader* image = sAllImages[i+1];//1過濾到主程序。
                link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
                image->setNeverUnloadRecursive();
            }
            // 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);
            }
        }

0x06 初始化函數(shù)坯癣,承前啟后

一瓶盛、dyld流程分析

-> 在main函數(shù)中我們進(jìn)入initializeMainExecutable
->runInitializers初始化主程序
->processInitializers
->recursiveInitialization循環(huán)初始化
->關(guān)鍵函數(shù) :notifySingle。在這個方法中調(diào)用了objcloadImages。通過command+shift+o全局搜索尋找實(shí)現(xiàn)惩猫。

static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)

發(fā)現(xiàn)一個函數(shù)指針的調(diào)用:

(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());

通過在本文件搜索sNotifyObjCInit函數(shù)指針芝硬,我們找到了賦值的地方。

void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
    sNotifyObjCInit     = init;//賦值函數(shù)帆锋。

全局搜索registerObjCNotifiers調(diào)用的地方

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便找不到這個方法調(diào)用吵取。于是我們通過Xcode設(shè)置符號斷點(diǎn)來分析。如圖所示锯厢,我們則能推斷出這個方法的調(diào)用是在runtime中皮官。

二、runtime流程分析

分析runtime源碼实辑∞嗲猓可知上面的函數(shù)是在其初始化的時候進(jìn)行調(diào)用的。
load_images賦值到dyld中的sNotifyObjCInit指針剪撬。

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();
    lock_init();
    exception_init();
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

load_images中摄乒,完成call_load_methods的調(diào)用。

load_images(const char *path __unused, const struct mach_header *mh)
{
    // 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中残黑,通過doWhile循環(huán)來調(diào)用call_class_loads實(shí)現(xiàn)每個類的load方法馍佑。

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();

    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        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;
}

三、_ _ attribute_ _((constructor))

是GCC的擴(kuò)展語法(黑魔法)梨水,由它修飾過的函數(shù)拭荤,會在main函數(shù)之前調(diào)用。原理是在ELF的.ctors段增加一條函數(shù)引用疫诽,加載器在執(zhí)行main函數(shù)前舅世,檢查.ctror section,并執(zhí)行里面的函數(shù)奇徒。

繼續(xù)dyld分析雏亚。在imageLoader.cpp文件中,notifySingle調(diào)用之后摩钙,接著調(diào)用了doInitialization方法罢低。

其中doModInitFunctions會調(diào)用machO文件中_mod_init_func段的函數(shù),也就是我們在文件中所定義的全局C++構(gòu)造函數(shù)腺律。

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

通過以上分析加載流程我們可得知函數(shù)的執(zhí)行順序?yàn)椋?/p>

load -> attribute((constructor)) -> main -> initialize

0x07 尋找應(yīng)用程序主函數(shù)入口

最后return,dyld的main函數(shù)結(jié)束奕短。

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

至此,程序進(jìn)入了main函數(shù)匀钧,開啟了我們熟知的一切翎碑。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市之斯,隨后出現(xiàn)的幾起案子日杈,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件莉擒,死亡現(xiàn)場離奇詭異酿炸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)涨冀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進(jìn)店門填硕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鹿鳖,你說我怎么就攤上這事扁眯。” “怎么了翅帜?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵姻檀,是天一觀的道長。 經(jīng)常有香客問我涝滴,道長绣版,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任歼疮,我火速辦了婚禮杂抽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘韩脏。我一直安慰自己默怨,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布骤素。 她就那樣靜靜地躺著,像睡著了一般愚屁。 火紅的嫁衣襯著肌膚如雪济竹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天霎槐,我揣著相機(jī)與錄音送浊,去河邊找鬼。 笑死丘跌,一個胖子當(dāng)著我的面吹牛袭景,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播闭树,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼耸棒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了报辱?” 一聲冷哼從身側(cè)響起与殃,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后幅疼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體米奸,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年爽篷,在試婚紗的時候發(fā)現(xiàn)自己被綠了悴晰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡逐工,死狀恐怖铡溪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情钻弄,我是刑警寧澤佃却,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站窘俺,受9級特大地震影響饲帅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瘤泪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一灶泵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧对途,春花似錦赦邻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至膳犹,卻和暖如春恬吕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背须床。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工铐料, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人豺旬。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓钠惩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親族阅。 傳聞我的和親對象是個殘疾皇子篓跛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評論 2 355

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