找回密码
 会员注册
查看: 48|回复: 0

从源码分析类的加载过程

[复制链接]

1

主题

0

回帖

4

积分

新手上路

积分
4
发表于 2024-9-25 13:04:46 | 显示全部楼层 |阅读模式
在分析类的加载过程之前,我们先了解一下应用的加载过程。我们知道APP由内核引导启动后,系统会先读取APP内的可执行文件,从里面获取动态链接器路径,然后加载dyld初始化环境,并配合ImageLoder将二进制文件按格式加载到内存中,链接依赖库调用每个依赖库的初始化方法,这时候runtime也会被初始化,当所有依赖库初始化之后,最后到程序可执行文件进行初始化,在这时runtime会对所有类进行类结构初始化,调用所有load方法。最后dyld返回main函数地址,main()函数被调用。其中dyld和objc相互配合,动态链接器dyld在加载动态库的过程中初始化objc,objc在初始化过程中注册回调函数_dyld_objc_notify_register,通知dyld执行map_image\load_image\unmap_image。  从截图中我们可以看到类的整个加载流程是从dyld-->libSystem-->libdispatch-->objc中的_objc_int,而在_objc_init中又调用了_dyld_objc_notify_register注册回调函数。下面我们结合源码分析一下类的加载过程。_objc_init方法/************************************************************************_objc_init*Bootstrapinitialization.Registersourimagenotifierwithdyld.*CalledbylibSystemBEFORElibraryinitializationtime**********************************************************************/void_objc_init(void){staticboolinitialized=false;if(initialized)return;initialized=true;//fixmedeferinitializationuntilanobjc-usingimageisfound?environ_init();//!!初始化一系列环境变量并读取影响运行时的环境变量tls_init();//!!处理线程key的绑定static_init();//!!运行C++静态构造函数lock_init();//!!空函数exception_init();//!!初始化libobjc异常处理系统_dyld_objc_notify_register(&map_images,load_images,unmap_image);//!!dyld回调函数}environ_init()environ_init方法主要是初始化一系列环境变量,并读取影响运行时的环境变量。/************************************************************************environ_init*Readenvironmentvariablesthataffecttheruntime.*Alsoprintenvironmentvariablehelp,ifrequested.**********************************************************************/voidenviron_init(void){if(issetugid()){//Allenvironmentvariablesaresilentlyignoredwhensetuidorsetgid//ThisincludesOBJC_HELPandOBJC_PRINT_OPTIONSthemselves.return;}boolPrintHelp=false;boolPrintOptions=false;boolmaybeMallocDebugging=false;//Scanenviron[]directlyinsteadofcallinggetenv()alot.//Thisoptimizesthecasewherenoneareset.for(char**p=*_NSGetEnviron();*p!=nil;p++){...}//Specialcase:enablesomeautoreleasepooldebugging//whensomemallocdebuggingisenabled//andOBJC_DEBUG_POOL_ALLOCATIONisnotsettosomethingotherthanNO.if(maybeMallocDebugging){...}//PrintOBJC_HELPandOBJC_PRINT_OPTIONSoutput.if(PrintHelp||PrintOptions){...}}从上面的源码中我们可以通过运行查看影响运行时的环境变量或者通过终端输入exportOBJC_HELP=1查看,下面截图只是部分,读者可以自行打印查看。  tls_init()tls_init方法主要是处理线程key的绑定,处理每个线程数据的析构函数。static_init()staic_init方法是运行C++静态构造函数/************************************************************************static_init*RunC++staticconstructorfunctions.*libccalls_objc_init()beforedyldwouldcallourstaticconstructors,libc在调用dyld的_dyld_objc_notify_register函数之前调用*sowehavetodoitourselves.**********************************************************************/staticvoidstatic_init(){size_tcount;autoinits=getLibobjcInitializers(&_mh_dylib_header,&count);for(size_ti=0;icall'init'functiononallimagesalreadyinit'ed(belowlibSystem)for(std::vector::iteratorit=sAllImages.begin();it!=sAllImages.end();it++){ImageLoader*image=*it;if((image->getState()==dyld_image_state_initialized)&image->notifyObjC()){dyld3::ScopedTimertimer(DBG_DYLD_TIMING_OBJC_INIT,(uint64_t)image->machHeader(),0,0);(*sNotifyObjCInit)(image->getRealPath(),image->machHeader());}}}static_dyld_objc_notify_mappedsNotifyObjCMapped;static_dyld_objc_notify_initsNotifyObjCInit;static_dyld_objc_notify_unmappedsNotifyObjCUnmapped;从上图中可以看出从libobjc传过来的三个函数指针被保存在dyld库的三个本地静态变量中sNotifyObjCMapped、sNOtifyObjCInit、sNotifyObjCUnmapped,从截图中可知调用notifyBatchPartial()方法来映射所有的镜像。从notifyBatchPartial函数内部实现来看会通过sNotifyObjCMapped函数指针调用告诉objc镜像全部映射完成。而三个函数指针内部具体做了什么,我们继续探究。map_images当dyld将镜像加载到内存的时候会触发该函数。/************************************************************************map_images*Processthegivenimageswhicharebeingmappedinbydyld.*CallsABI-agnosticcodeaftertakingABI-specificlocks.*Locking:write-locksruntimeLock**********************************************************************/voidmap_images(unsignedcount,constchar*constpaths[],conststructmach_header*constmhdrs[]){mutex_locker_tlock(runtimeLock);returnmap_images_nolock(count,paths,mhdrs);}在map_images函数中会调用map_images_nolock函数,其中hCount是镜像文件个数,调用_read_images()来加载镜像文件,执行所有类的注册和修复功能。voidmap_images_nolock(unsignedmhCount,constchar*constmhPaths[],conststructmach_header*constmhdrs[]){.....(省略)if(hCount>0){_read_images(hList,hCount,totalClasses,unoptimizedTotalClasses);}}而_read_images()是我们关注的重点。下面我们来看一下_read_images()函数的调用。_read_images由于_read_images函数中代码很多,我们分步来探究其具体实现,1、doneOnce流程:if(!doneOnce){doneOnce=YES;.......if(DisableTaggedPointers){disableTaggedPointers();}initializeTaggedPointerObfuscator();if(PrintConnecting){_objc_inform("CLASS:found%dclassesduringlaunch",totalClasses);}//namedClasses//Preoptimizedclassesdon'tgointhistable.//4/3isNXMapTable'sloadfactorintnamedClassesSize=(isPreoptimized()?unoptimizedTotalClasses:totalClasses)*4/3;gdb_objc_realized_classes=NXCreateMapTable(NXStrValueMapPrototype,namedClassesSize);allocatedClasses=NXCreateHashTable(NXPtrPrototype,0,nil);ts.log("IMAGETIMES:firsttimetasks");}/************************************************************************getClass*Looksupaclassbyname.TheclassMIGHTNOTberealized.*DemangledSwiftnamesarerecognized.*Locking:runtimeLockmustberead-orwrite-lockedbythecaller.**********************************************************************///Thisisamisnomer:gdb_objc_realized_classesisactuallyalistof//namedclassesnotinthedyldsharedcache,whetherrealizedornot.NXMapTable*gdb_objc_realized_classes;//exportedfordebuggersinobjc-gdb.h/************************************************************************allocatedClasses*Atableofallclasses(andmetaclasses)whichhavebeenallocated*withobjc_allocateClassPair.**********************************************************************/staticNXHashTable*allocatedClasses=nil;通过doneOnce流程会创建两个表gdb_objc_realized_classes、allocatedClasses,其中gdb_objc_realized_classes存储不再共享缓存且已经命名的所有类,其容量是类数量的4/3,allocatedClasses是存储已经初始化的类。2、类的重映射//Discoverclasses.Fixupunresolvedfutureclasses.Markbundleclasses.for(EACH_HEADER){classref_t*classlist=_getObjc2ClassList(hi,&count);//!!从编译后的类列表中取出所有类,获取到的是一个classref_t类型的指针if(!mustReadClasses(hi)){//ImageissufficientlyoptimizedthatweneednotcallreadClass()continue;}boolheaderIsBundle=hi->isBundle();boolheaderIsPreoptimized=hi->isPreoptimized();for(i=0;iisAnySwift()){_objc_fatal("Can'tcompletefutureclassrequestfor'%s'""becausetherealclassistoobig.",cls->nameForLogging());}class_rw_t*rw=newCls->data();constclass_ro_t*old_ro=rw->ro;memcpy(newCls,cls,sizeof(objc_class));rw->ro=(class_ro_t*)newCls->data();newCls->setData(rw);freeIfMutable((char*)old_ro->name);free((void*)old_ro);addRemappedClass(cls,newCls);replacing=cls;cls=newCls;}if(headerIsPreoptimized&!replacing){//classlistbuiltinsharedcache//fixmestrictassertdoesn'tworkbecauseofduplicates//assert(cls==getClass(name));assert(getClass(mangledName));}else{addNamedClass(cls,mangledName,replacing);addClassTableEntry(cls);}//forfuturereference:sharedcachenevercontainsMH_BUNDLEsif(headerIsBundle){cls->data()->flags|=RO_FROM_BUNDLE;cls->ISA()->data()->flags|=RO_FROM_BUNDLE;}returncls;}运行源码断点查看进入readclass()后会调用addNamedClass()和addClassTableEntry(),而addNamedClass内部是将cls插入到gdb_objc_realized_classes表中。/************************************************************************addNamedClass*Addsname=>clstothenamednon-metaclassmap.*Warnsaboutduplicateclassnamesandkeepstheoldmapping.*Locking:runtimeLockmustbeheldbythecaller**********************************************************************/staticvoidaddNamedClass(Classcls,constchar*name,Classreplacing=nil){runtimeLock.assertLocked();Classold;if((old=getClass(name))&old!=replacing){inform_duplicate(name,old,cls);//getNonMetaClassusesnamelookups.Classesnotfoundbyname//lookupmustbeinthesecondarymeta->nonmetatable.addNonMetaClass(cls);}else{NXMapInsert(gdb_objc_realized_classes,name,cls);}assert(!(cls->data()->flags&RO_META));//wrong:constructedclassesarealreadyrealizedwhentheygethere//assert(!cls->isRealized());}addClassTableEntry内部是将cls插入到allocatedClasses表中/************************************************************************addClassTableEntry*Addaclasstothetableofallclasses.IfaddMetaistrue,*automaticallyaddsthemetaclassoftheclassaswell.*Locking:runtimeLockmustbeheldbythecaller.**********************************************************************/staticvoidaddClassTableEntry(Classcls,booladdMeta=true){runtimeLock.assertLocked();//Thisclassisallowedtobeaknownclassviathesharedcacheorvia//datasegments,butitisnotallowedtobeinthedynamictablealready.assert(!NXHashMember(allocatedClasses,cls));if(!isKnownClass(cls))NXHashInsert(allocatedClasses,cls);if(addMeta)addClassTableEntry(cls->ISA(),false);}readClass中的调用通过popFutureNamedClass判断是否是后期要处理的类,如果是的话,就取出后期处理的类,读取这个类的data类设置ro/rw相关信息;addNamedClass插入总表,addClassTableEntry插入已开辟内存的类的表。3、修复重映射将未映射的Class和supperClass重映射,调用_getObjc2ClassRefs()获取类的引用,_getObjc2SuperRefs()获取父类的引用,然后通过remapClasRef()进行重映射。//Fixupremappedclasses//Classlistandnonlazyclasslistremainunremapped.//Classrefsandsuperrefsareremappedformessagedispatching.if(!noClassesRemapped()){for(EACH_HEADER){Class*classrefs=_getObjc2ClassRefs(hi,&count);for(i=0;iisPreoptimized())continue;boolisBundle=hi->isBundle();SEL*sels=_getObjc2SelectorRefs(hi,&count);UnfixedSelectors+=count;for(i=0;ifname());}for(i=0;iisPreoptimized();boolisBundle=hi->isBundle();protocol_t**protolist=_getObjc2ProtocolList(hi,&count);for(i=0;icache._buckets==(void*)&_objc_empty_cache&(cls->cache._mask||cls->cache._occupied)){cls->cache._mask=0;cls->cache._occupied=0;}if(cls->ISA()->cache._buckets==(void*)&_objc_empty_cache&(cls->ISA()->cache._mask||cls->ISA()->cache._occupied)){cls->ISA()->cache._mask=0;cls->ISA()->cache._occupied=0;}#endifaddClassTableEntry(cls);realizeClass(cls);}} 实现了+load方法的类是懒加载类,通过_getObjc2NonLazyClassList()方法获取到__objc_nlclslist,取出非懒加载类,addClassTableEntry()加载,如果已添加不会再添加,确保整个结构添加。realizeClass()实现所有非懒加载类,实例化类对象的一些信息。9、初始化新解析出来的future类//Realizenewly-resolvedfutureclasses,incaseCFmanipulatesthemif(resolvedFutureClasses){for(i=0;isetInstancesRequireRawIsa(false/*inherited*/);}free(resolvedFutureClasses);}10、处理所有的分类包括类和元类至此_read_images()流程分析完毕,其具体流程如下图:load_images接下来我们来分析_dyld_objc_notify_register的第二个参数load_images,load_images是在什么时候调用呢?我们查看dyld源码中搜索对应的函数指针sNotifyObjCInit可知在notifySingle内部该函数指针被调用。_load_images是对每一个加载进来的可执行文件镜像都会递归调用一次。/************************************************************************load_images*Process+loadinthegivenimageswhicharebeingmappedinbydyld.**Locking:write-locksruntimeLockandloadMethodLock**********************************************************************/externboolhasLoadMethods(constheaderType*mhdr);externvoidprepare_load_methods(constheaderType*mhdr);voidload_images(constchar*path__unused,conststructmach_header*mh){//Returnwithouttakinglocksifthereareno+loadmethodshere.if(!hasLoadMethods((constheaderType*)mh))return;recursive_mutex_locker_tlock(loadMethodLock);//Discoverloadmethods{mutex_locker_tlock2(runtimeLock);prepare_load_methods((constheaderType*)mh);}//Call+loadmethods(withoutruntimeLock-re-entrant)call_load_methods();}处理dyld映射的给定镜像中的load方法,判断是否有load方法,如果没有直接返回,通过prepare_load_methods探索load方法将每个动态库中要执行load方法的类插入到一个表中,再通过call_load_methods方法调用load方法,call_load_methods方法中调用类和分类的load方法,类里面的load方法是父类优先调用,之后调用分类的load方法。总结到这里,从_objc_init到_dyld_objc_notify_register的过程我们分析完成,其中的源码都是关键部分的截图,大家有时间可以自行下载查看其源码的具体实现。通过了解应用的加载过程及类的加载过程及,我们可以了解main()函数调用之前的加载过程,对于客户端优化启动速度有一定帮助。其类加载的大致流程图如下:参考资料:1、https://opensource.apple.com/tarballs/dyld/dyld-832.7.3.tar.gz2、https://opensource.apple.com/tarballs/objc4/3、https://stackoverflow.com/questions/39863112/what-is-required-for-a-mach-o-executable-to-load
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-25 13:37 , Processed in 0.543942 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表