preface
From the previous analysis of the loading process of iOS Applications, we have some understanding of the loading process of programs. It is through objc_init() method to register the callback function, and then load the image file. Then when the image file is loaded, how to read the memory? In what way is it stored? Next we analyze the libObjc source code, to understand.
1.Objc_init analysis
Check out the objc_init method in libObjc’s source code as follows:
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);
}
Copy the code
1.1Environ_init () environment variable
void environ_init(void)
{
if (issetugid()) {
// All environment variables are silently ignored when setuid or setgid
// This includes OBJC_HELP and OBJC_PRINT_OPTIONS themselves.
return;
}
bool PrintHelp = false;
bool PrintOptions = false;
bool maybeMallocDebugging = false;
// Scan environ[] directly instead of calling getenv() a lot.
// This optimizes the case where none are set.
for(char **p = *_NSGetEnviron(); *p ! = nil; p++) {if (0 == strncmp(*p, "Malloc", 6) || 0 == strncmp(*p, "DYLD", 4) ||
0 == strncmp(*p, "NSZombiesEnabled", 16))
{
maybeMallocDebugging = true;
}
if (0 != strncmp(*p, "OBJC_", 5)) continue;
if (0 == strncmp(*p, "OBJC_HELP=", 10)) {
PrintHelp = true;
continue;
}
if (0 == strncmp(*p, "OBJC_PRINT_OPTIONS=", 19)) {
PrintOptions = true;
continue;
}
const char *value = strchr(*p, '=');
if(! *value)continue;
value++;
for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
const option_t *opt = &Settings[i];
if ((size_t)(value - *p) == 1+opt->envlen &&
0 == strncmp(*p, opt->env, opt->envlen))
{
*opt->var = (0 == strcmp(value, "YES"));
break;
}
}
}
// Special case: enable some autorelease pool debugging
// when some malloc debugging is enabled
// and OBJC_DEBUG_POOL_ALLOCATION is not set to something other than NO.
if (maybeMallocDebugging) {
const char *insert = getenv("DYLD_INSERT_LIBRARIES");
const char *zombie = getenv("NSZombiesEnabled");
const char *pooldebug = getenv("OBJC_DEBUG_POOL_ALLOCATION");
if ((getenv("MallocStackLogging")
|| getenv("MallocStackLoggingNoCompact")
|| (zombie && (*zombie == 'Y' || *zombie == 'y'))
|| (insert && strstr(insert, "libgmalloc"&& ()))! pooldebug || 0 == strcmp(pooldebug,"YES")))
{
DebugPoolAllocation = true; }} /* Modify the judgment criteria yourself and print the environment variables.for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
const option_t *opt = &Settings[i];
_objc_inform("%s: %s", opt->env, opt->help);
if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
}
*/
// Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
if (PrintHelp || PrintOptions) {
if (PrintHelp) {
_objc_inform("Objective-C runtime debugging. Set variable=YES to enable.");
_objc_inform("OBJC_HELP: describe available environment variables");
if (PrintOptions) {
_objc_inform("OBJC_HELP is set");
}
_objc_inform("OBJC_PRINT_OPTIONS: list which options are set");
}
if (PrintOptions) {
_objc_inform("OBJC_PRINT_OPTIONS is set");
}
for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
const option_t *opt = &Settings[i];
if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env); }}}Copy the code
You can print all environment variables by modifying the judgment criteria, as shown in the comments section of the source code. We can also print environment variables on LLDB with the command export OBJC_HELP=1.
Environment variables:
objc[36792]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[36792]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[36792]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[36792]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods
objc[36792]: OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod:
objc[36792]: OBJC_PRINT_CLASS_SETUP: log progress of class and category setup
objc[36792]: OBJC_PRINT_PROTOCOL_SETUP: log progress of protocol setup
objc[36792]: OBJC_PRINT_IVAR_SETUP: log processing of non-fragile ivars
objc[36792]: OBJC_PRINT_VTABLE_SETUP: log processing of class vtables
objc[36792]: OBJC_PRINT_VTABLE_IMAGES: print vtable images showing overridden methods
objc[36792]: OBJC_PRINT_CACHE_SETUP: log processing of method caches
objc[36792]: OBJC_PRINT_FUTURE_CLASSES: log use of future classes for toll-free bridging
objc[36792]: OBJC_PRINT_PREOPTIMIZATION: log preoptimization courtesy of dyld shared cache
objc[36792]: OBJC_PRINT_CXX_CTORS: log calls to C++ ctors and dtors for instance variables
objc[36792]: OBJC_PRINT_EXCEPTIONS: log exception handling
objc[36792]: OBJC_PRINT_EXCEPTION_THROW: log backtrace of every objc_exception_throw()
objc[36792]: OBJC_PRINT_ALT_HANDLERS: log processing of exception alt handlers
objc[36792]: OBJC_PRINT_REPLACED_METHODS: log methods replaced by category implementations
objc[36792]: OBJC_PRINT_DEPRECATION_WARNINGS: warn about calls to deprecated runtime functions
objc[36792]: OBJC_PRINT_POOL_HIGHWATER: log high-water marks for autorelease pools
objc[36792]: OBJC_PRINT_CUSTOM_RR: log classes with un-optimized custom retain/release methods
objc[36792]: OBJC_PRINT_CUSTOM_AWZ: log classes with un-optimized custom allocWithZone methods
objc[36792]: OBJC_PRINT_RAW_ISA: log classes that require raw pointer isa fields
objc[36792]: OBJC_DEBUG_UNLOAD: warn about poorly-behaving bundles when unloaded
objc[36792]: OBJC_DEBUG_FRAGILE_SUPERCLASSES: warn about subclasses that may have been broken by subsequent changes to superclasses
objc[36792]: OBJC_DEBUG_NIL_SYNC: warn about @synchronized(nil), which does no synchronization
objc[36792]: OBJC_DEBUG_NONFRAGILE_IVARS: capriciously rearrange non-fragile ivars
objc[36792]: OBJC_DEBUG_ALT_HANDLERS: record more info about bad alt handler use
objc[36792]: OBJC_DEBUG_MISSING_POOLS: warn about autorelease with no pool in place, which may be a leak
objc[36792]: OBJC_DEBUG_POOL_ALLOCATION: halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools
objc[36792]: OBJC_DEBUG_DUPLICATE_CLASSES: halt when multiple classes with the same name are present
objc[36792]: OBJC_DEBUG_DONT_CRASH: halt the process by exiting instead of crashing
objc[36792]: OBJC_DISABLE_VTABLES: disable vtable dispatch
objc[36792]: OBJC_DISABLE_PREOPTIMIZATION: disable preoptimization courtesy of dyld shared cache
objc[36792]: OBJC_DISABLE_TAGGED_POINTERS: disable tagged pointer optimization of NSNumber et al.
objc[36792]: OBJC_DISABLE_TAG_OBFUSCATION: disable obfuscation of tagged pointers
objc[36792]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
objc[36792]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork
Copy the code
The above code is our Environment Variables, you can set Environment Variables in Xcode, Edit Scheme->Arguments->Environment Variables
- Set up the
OBJC_DISABLE_NONPOINTER_ISA
为YES
Set:nonpointer_isa
To optimize the memory structure. - Set up the
OBJC_PRINT_LOAD_METHODS
forYES
: Can print all implementations nowload
Method class, to optimize the program to help start, can be faster to find implementationload
Method class. - Set up the
OS_ACTIVITY_MODE
fordisable
, you can mask system logs.
1.2tls_init()
Bind the thread key
void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
_objc_pthread_key = TLS_DIRECT_KEY;
pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
_objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}
Copy the code
1.3static_init()
Run the C++ static constructor, and libc calls the _objc_init() method before dyld loads the static constructor. (See note)
/***********************************************************************
* static_init
* Run C++ static constructor functions.
* libc calls _objc_init() before dyld would call our static constructors,
* so we have to do it ourselves.
**********************************************************************/
static void static_init()
{
size_t count;
auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
for(size_t i = 0; i < count; i++) { inits[i](); }}Copy the code
1.4lock_init()
void lock_init(void)
{
}
Copy the code
Empty and written in C++, ObjC uses the same locking mechanisms of C++ and C, but just wraps them on top.
1.5exception_init()
Exception handling function, source code as follows:
Initialize libobJC’s exception handling system
/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system. * Called by map_images(). * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / / / initializes the libobjc exception handling system void exception_init (void) { old_terminate = std::set_terminate(&_objc_terminate); }Copy the code
This method checks for an objC exception by calling our registered callback object callback (uncaught_handler).
/*
_objc_terminate
Custom std::terminate handler.
The uncaught exception callback is implemented as a std::terminate handler.
1. Check if there's an active exception
2. If so, check if it's an Objective-C exception
3. If so, call our registered callback with the object.
4. Finally, call the previous terminate handler.
*/
static void (*old_terminate)(void) = nil;
static void _objc_terminate(void)
{
if (PrintExceptions) {
_objc_inform("EXCEPTIONS: terminating");
}
if (! __cxa_current_exception_type()) {
// No current exception.
(*old_terminate)();
}
else {
// There is a current exception. Check if it's an objc exception. @try { __cxa_rethrow(); } @catch (id e) { // It's an objc object. Call Foundation's handler, if any. (*uncaught_handler)((id)e); (*old_terminate)(); } @catch (...) { // It's not an objc object. Continue to C++ terminate. (*old_terminate)(); }}}Copy the code
1.5_dyld_objc_notify_register()
The source code is as follows:
//
// Note: only for use by objc runtime
// Register handlers to be called when objc images are mapped, unmapped, and initialized.
// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.
// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to
// call dlopen() on them to keep them from being unloaded. During the call to _dyld_objc_notify_register(),
// dyld will call the "mapped" function with already loaded objc images. During any later dlopen() call,
// dyld will also call the "mapped" function. Dyld will call the "init" function when dyld would be called
// initializers in that image. This is when objc calls any +load methods in that image.
//
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped);
Copy the code
Looking at the comments, you can see that this method is only used by the OBJC runtime, the register handler that is called when objC images are mapped, unmapped, and initialized. Dyld will call the mapped function with the image array containing the objC-image-info part. When Dyld is called, the init function is called by Dyld.
_dyld_objc_notify_register(&map_images, load_images, unmap_image)
Let’s explore the three parameters of _DYLD_OBJC_Notify_register
-
map_images
-
load_images
-
unmap_image
2.map_images
See map_images source code below:
/***********************************************************************
* map_images
* Process the given images which are being mapped inby dyld. * Calls ABI-agnostic code after taking ABI-specific locks. * * Locking: write-locks runtimeLock **********************************************************************/ void map_images(unsigned count, const char * const paths[], const struct mach_header * const mhdrs[]) { mutex_locker_t lock(runtimeLock);return map_images_nolock(count, paths, mhdrs);
}
Copy the code
To analyze it by comment:
- Processing by
dyld
To a given image file. - Then enter the
map_images_nolock
function
Analyze the map_images_NOLock function, remove printing and some operations on hCount, and directly locate the following code:
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
Copy the code
Enter the _read_images image file to read.
2.1_read_images
Since the source code for _read_images is too long, we analyze the key code:
#define EACH_HEADER \
hIndex = 0; \
hIndex < hCount && (hi = hList[hIndex]); \
hIndex++
if (!doneOnce) {
doneOnce = YES;
#if SUPPORT_NONPOINTER_ISA
// Disable non-pointer isa under some conditions.
# if SUPPORT_INDEXED_ISA
// Disable nonpointer isa if any image contains old Swift code
for (EACH_HEADER) {
if (hi->info()->containsSwift() &&
hi->info()->swiftUnstableVersion() < objc_image_info::SwiftVersion3)
{
DisableNonpointerIsa = true;
if (PrintRawIsa) {
_objc_inform("RAW ISA: disabling non-pointer isa because "
"the app or a framework contains Swift code "
"older than Swift 3.0");
}
break; }}# endif
# if TARGET_OS_OSX
// Disable non-pointer isa if// (Linked before OS X 10.11)if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_11) {
DisableNonpointerIsa = true;
if (PrintRawIsa) {
_objc_inform("RAW ISA: disabling non-pointer isa because "
"the app is too old (SDK version " SDK_FORMAT ")",
FORMAT_SDK(dyld_get_program_sdk_version()));
}
}
// Disable non-pointer isa if the app has a __DATA,__objc_rawisa section
// New apps that load old extensions may need this.
for (EACH_HEADER) {
if(hi->mhdr()->filetype ! = MH_EXECUTE)continue;
unsigned long size;
if (getsectiondata(hi->mhdr(), "__DATA"."__objc_rawisa", &size)) {
DisableNonpointerIsa = true;
if (PrintRawIsa) {
_objc_inform("RAW ISA: disabling non-pointer isa because "
"the app has a __DATA,__objc_rawisa section"); }}break; // assume only one MH_EXECUTE image
}
# endif
#endif
if (DisableTaggedPointers) {
disableTaggedPointers();
}
initializeTaggedPointerObfuscator();
if (PrintConnecting) {
_objc_inform("CLASS: found %d classes during launch", totalClasses);
}
// namedClasses
// Preoptimized classes don't go in this table. // 4/3 is NXMapTable's load factor
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);
ts.log("IMAGE TIMES: first time tasks");
}
Copy the code
- Read the above source code when
doneOnce
forNO
When you enter the capital for the first timeif
Judge the inside and then takedoneOnce
Modified toYES
So this judgment will only be made once, the first time it comes in. Next, look at the key source code in the judgment:
// namedClasses
// Preoptimized classes don't go in this table.
// 4/3 is NXMapTable's load factor
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);
Copy the code
View the source code comment:
1. Gdb_objc_realized_classes is where all classes, whether implemented or not, are stored. 3. The gDB_objC_realized_classes table contains the allocatedClasses tableCopy the code
Hence: in _read_images, all classes are loaded into the gDB_objC_realized_classes table first, so why create two tables? You don’t have to carry around a big table every time. When not found in allocatedClasses, the class is not initialized, so there is no need to look in gdb_objC_realized_classes.
Once you have created two tables, how do you add all the classes to the table? Let’s move on
- Remap all classes
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
for(EACH_HEADER) {classref_t * classList = _getObjc2ClassList(hi, &count);if (! mustReadClasses(hi)) {
// Image is sufficiently optimized that we need not call readClass()
continue;
}
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->isPreoptimized();
for(i = 0; i < count; I++) {OS_dispatch_queue_concurrent, OS_xpc_object, NSRunloop, CF, Fundation, libdispatch, etc. Class CLS = (Class) classList [I]; / / byreadThe Class function gets the processed new Class, Class newCls =readClass(cls, headerIsBundle, headerIsPreoptimized); // Initialize the memory required by all lazily loaded classes - the data is not currently loaded - not even the class initializationif(newCls ! = cls && newCls) { // Class was moved but not deleted. Currently this occurs // only when the new class resolved a Future class. // Non-lazily realize the class below. realloc(resolvedFutureClasses, (resolvedFutureClassCount+1) * sizeof(Class)); resolvedFutureClasses[resolvedFutureClassCount++] = newCls; }}}Copy the code
The above section iterates all classes from the compiled class list and adds them to the corresponding gDB_objC_realized_CLASSES and allocatedClasses tables. So, where was this added? Let’s leave a question and explore the _read_images process.
- Register all SEL to
namedSelectors
In the hash table,
// Fix up @selector references static size_t UnfixedSelectors; { mutex_locker_t lock(selLock);for (EACH_HEADER) {
if (hi->isPreoptimized()) continue;
bool isBundle = hi->isBundle();
SEL *sels = _getObjc2SelectorRefs(hi, &count);
UnfixedSelectors += count;
for(i = 0; i < count; i++) { const char *name = sel_cname(sels[i]); Sels [I] = sel_registerNameNoLock(name, isBundle); }}}Copy the code
- Add all protocols to
protocol_map
Protocol table, and on allProtocol
Heavy mapping
// Discover protocols. Fix up protocol refs. // Iterate over all protocol lists and load the protocol list into the protocol hash tablefor (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol; // CLS = Protocol. All protocols and objects have similar structures. Isa corresponds to Protocol Class CLS = (Class)&OBJC_CLASS_$_Protocol; assert(cls); NXMapTable *protocol_map = protocols(); bool isPreoptimized = hi->isPreoptimized(); bool isBundle = hi->isBundle(); Protocol Protocol_t **protolist = _getObjc2ProtocolList(hi, &count);for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
ts.log("IMAGE TIMES: discover protocols");
Copy the code
- Initialize all non-lazy-loaded classes and proceed
ro
,rw
operation
// Realize non-lazy classes (for+load methods and static instances) // Implement non-lazy loading classes, for load methods and static instance variablesfor (EACH_HEADER) {
classref_t *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
// printf("non-lazy Class:%s\n",cls->mangledName());
if(! cls)continue;
// hack for class __ARCLite__, which didn't get this above #if TARGET_OS_SIMULATOR if (cls->cache._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; } #endif addClassTableEntry(cls); if (cls->isSwiftStable()) { if (cls->swiftMetadataInitializer()) { _objc_fatal("Swift class %s with a metadata initializer " "is not allowed to be non-lazy", cls->nameForLogging()); } // fixme also disallow relocatable classes // We can'T disallow all Swift classes because of // classes like Swift.__EmptyArrayStorage} For example, rw) realizeClassWithoutSwift (CLS); }}Copy the code
In this case, how do I assign rw and ro? Let’s start by asking questions and exploring the _read_images process.
- traverse
resolvedFutureClasses
Array, for labeled lazy-loaded classes, initialized
// Realize newly-resolved future classes, in caseCF pulates them // Traverse resolvedFutureClasses array, implement lazy loading classesif (resolvedFutureClasses) {
for (i = 0; i < resolvedFutureClassCount; i++) {
Class cls = resolvedFutureClasses[i];
if (cls->isSwiftStable()) {
_objc_fatal("Swift class is not allowed to be future"); } // Implement lazy class realizeClassWithoutSwift(CLS); cls->setInstancesRequireRawIsa(false/*inherited*/);
}
free(resolvedFutureClasses);
}
Copy the code
- Deal with all
category
, includingClass
andMeteClass
.
// Discover categories. // Discover all categoriesforCategory_t ** CATList = _getObjc2CategoryList(hi, &count); bool hasClassProperties = hi->info()->hasCategoryClassProperties();for(i = 0; i < count; I ++) {// Inner loop over all Category category_t *cat = catList [I]; Class cls = remapClass(cat->cls);if(! cls) { // Category's target class is missing (probably weak-linked). // Disavow any knowledge of this category. catlist[i] = nil; if (PrintConnecting) { _objc_inform("CLASS: IGNORING category \? \? \? (%s) %p with " "missing weak-linked target class", cat->name, cat); } continue; } // Process this category. // First, register the category with its target class. // Then, rebuild the class's method lists (etc) if// The class is realized. // First, register a Category with the class to which it belongs. If the class is already implemented, the list of methods for the class is reconstructed. bool classExists = NO;if(the cat - > instanceMethods | | cat - > separate protocols | | cat - > instanceProperties) {/ / add a Category to the corresponding value of the Class, Value is the Class all the corresponding category array addUnattachedCategoryForClass (cat, CLS, hi); // Add method, protocol, and property for the Category to the Classif (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : ""); } // Add a Category to the Meta Class. // Add a Category to the Classif (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)", cls->nameForLogging(), cat->name); }}}}Copy the code
At this point, the entire process of _read_images is analyzed. Now, for the questions left by this process,
Such as:
- All classes are iterated through the compiled class list and added to the corresponding
gdb_objc_realized_classes
andallocatedClasses
In the table - When loading non-lazily loaded classes, yeah
ro
,rw
How is assignment handled?
The next. We analyze the key steps.
2.2readClass
In the above process, we learned that the first step is to create two tables, gDB_objC_realized_classes and allocatedClasses, to store the classes. All classes are then remapped. System is through the readClass function for class processing, check readClass source code found the following source:
Look at the code seems to be the processing of RO, RW, but after the breakpoint debugging, found that the program did not enter here.
If (Class newCls = popFutureNamedClass(mangledName))
Then look at the readClass and find the following code:
addNamedClass(cls, mangledName, replacing);
addClassTableEntry(cls);
Copy the code
AddNamedClass addNamedClass
/*********************************************************************** * addNamedClass * Adds name => cls to the named non-meta class map. * Warns about duplicate class names and keeps the old mapping. * Locking: runtimeLock must be held by thecaller
**********************************************************************/
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
runtimeLock.assertLocked();
Class old;
if((old = getClassExceptSomeSwift(name)) && old ! = replacing) { inform_duplicate(name, old, cls); // getMaybeUnrealizedNonMetaClass uses name lookups. // Classes not found by name lookup must bein the
// secondary meta->nonmeta table.
addNonMetaClass(cls);
} else{ NXMapInsert(gdb_objc_realized_classes, name, cls); } assert(! (cls->data()->flags & RO_META)); // wrong: constructed classes are already realized when they get here // assert(! cls->isRealized()); }Copy the code
In addNamedClass, add the class to the previously created gDB_objC_realized_classes by NXMapInsert(gDB_objC_realized_classes, name, CLS).
AddClassTableEntry source code:
/*********************************************************************** * addClassTableEntry * Add a class to the table of all classes. If addMeta istrue,
* automatically adds the metaclass of the class as well.
* Locking: runtimeLock must be held by the caller.
**********************************************************************/
static void addClassTableEntry(Class cls, bool addMeta = true) {
runtimeLock.assertLocked();
// This class is allowed to be a known class via the shared cache or via
// data segments, but it is not allowed to be inthe dynamic table already. assert(! NXHashMember(allocatedClasses, cls));if(! isKnownClass(cls)) NXHashInsert(allocatedClasses, cls);if (addMeta)
addClassTableEntry(cls->ISA(), false);
}
Copy the code
In addClassTableEntry, add the class to the allocatedClasses you created earlier because it has already allocated space initially.
- Yes
readClass
In the methodallocatedClasses
andaddNamedClass
Two methods to add a class to the created table.
After remapping the class through readClass, the two classes are compared as shown in the following code:
In general, they are equal. When there are lazy-loaded classes, they do something in readClass that makes newCls and CLS not equal, and then add them to the array. The lazy-loaded class is then initialized, as described above.
2.3realizeClassWithoutSwift
How do we assign values to rw and ro when initializing all classes that are not lazily loaded? View the source code mainly has the following steps:
remapClass
Remap the classaddClassTableEntry(cls)
, inserts the class intoallocatedClasses
Table, if exist, do not insertrealizeClassWithoutSwift(cls)
, implement all non-lazy-loaded classes (instantiate some information about the class object, such as RW)
So we mainly look at realizeClassWithoutSwift source code:
/***********************************************************************
* realizeClassWithoutSwift
* Performs first-time initialization on class cls,
* including allocating its read-write data.
* Does not perform any Swift-side initialization.
* Returns the real class structure for the class.
* Locking: runtimeLock must be write-locked by the caller* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / / / the class CLS performed on the first initialized, including distribution, speaking, reading and writing data, does not perform any initialization Swift end. static Class realizeClassWithoutSwift(Class cls) { runtimeLock.assertLocked(); // initialize ro rw parent metaclass const class_ro_t *ro; class_rw_t *rw; Class supercls; Class metacls; bool isMeta; // To judge the class, there is recursion below, isa's classic bitmap, and finally the parent and metaclass point to nilif(! cls)return nil;
if (cls->isRealized()) return cls;
assert(cls == remapClass(cls));
// fixme verify class is not inan un-dlopened part of the shared cache? Ro = (const class_ro_t *) CLS ->data();if(ro-> tokens & RO_FUTURE) {// This was a future class. Rw data is already allocated. Rw = CLS ->data(); ro = cls->data()->ro; cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE); }elseRw ->ro (ro is already assigned at compile time); // Normal class.allocate writeable class data. rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1); rw->ro = ro; rw->flags = RW_REALIZED|RW_REALIZING; cls->setData(rw); } isMeta = ro->flags & RO_META; rw->version = isMeta ? 7:0; // old runtime went up to 6 ... Supercls = realizeClassWithoutSwift(remapClass(CLS ->superclass)); metacls = realizeClassWithoutSwift(remapClass(cls->ISA())); . // Update superclass and metaclassin caseIsa, CLS ->superclass = supercls; isa ->superclass = supercls; // Assign superclass to Class, CLS ->initClassIsa(metacls); // Initialize Class Isa, // Reconcile instance variable offsets / layout. // This may reallocate class_ro_t, updating our ro variable.if(supercls && ! isMeta) reconcileInstanceVariables(cls, supercls, ro); // Set fastInstanceSizeif it wasn't set already. cls->setInstanceSize(ro->instanceSize); . // Connect this class to its superclass'S subclass lists // Bidirectional lists point to subclasses that can be found in the relative parent class and subclasses that can be found in the subclass // List of subclasses linking this class to its superclassif (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
// Attach categories
methodizeClass(cls);
return cls;
}
Copy the code
Enter the methodizeClass
static void methodizeClass(Class cls) { runtimeLock.assertLocked(); Bool isMeta = CLS ->isMetaClass(); auto rw = cls->data(); auto ro = rw->ro; // Methodizingfor the first time
if (PrintConnecting) {
_objc_inform("CLASS: methodizing class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : ""); } // Install methods and properties that the class implements itself. method_list_t *list = ro->baseMethods(); // read the list of methods in roif(list) {// YES, assign baseMethods from ro to Methods prepareMethodLists(CLS, & List, 1, YES, isBundleClass(CLS)); rw->methods.attachLists(&list, 1); } property_list_t *proplist = ro->baseProperties; // Read the property list in roifAttachLists (&proplist, 1) {attachLists(&proplist, 1); } protocol_list_t *protolist = ro->baseProtocols; // Read the protocol list in roifAttachLists (&protolist, 1) {attachLists(&protolist, 1); } // Root classes get bonus method implementationsif they don't have // them already. These apply before category replacements. if (cls->isRootMetaclass()) { // root metaclass addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO); } // Attach categories. category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/); attachCategories(cls, cats, false /*don't flush caches*/);
if (PrintConnecting) {
if (cats) {
for (uint32_t i = 0; i < cats->count; i++) {
_objc_inform("CLASS: attached category %c%s(%s)",
isMeta ? '+' : The '-', cls->nameForLogging(), cats->list[i].cat->name); }}}if (cats) free(cats);
}
Copy the code
Rw is assigned when initializing a non-lazy-loaded class by calling realizeClassWithoutSwift to rw->ro and, in methodizeClass, to the other properties of RW.
While RO is assigned at compile time and can only be read, not changed, RW can dynamically add and process methods, properties, and protocols during debugging.
MethodizeClass, methods, attributes and protocols are assigned to THE RW using attachLists. Next, let’s look at attachLists source code
2.4AttachLists analysis
AttachLists source code:
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if(hasArray()) { // many lists -> many lists uint32_t oldCount = array()->count; //10 uint32_t newCount = oldCount + addedCount; / / 4setArray((array_t *)realloc(array(), array_t::byteSize(newCount))); array()->count = newCount; // 10+4 memmove(array()->lists + addedCount, array()->lists, oldCount * sizeof(array()->lists[0])); memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0])); }else if(! list && addedCount == 1) { // 0 lists -> 1 list list = addedLists[0]; }else{ // 1 list -> many lists List* oldList = list; uint32_t oldCount = oldList ? 1:0; uint32_t newCount = oldCount + addedCount;setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if(oldList) array()->lists[addedCount] = oldList; memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0])); }}Copy the code
There are three scenarios
- 0 to one, that is, 0 lists -> 1 list, directly through
addedLists
Direct assignment - 1 to many, that is: 1 list -> many lists, the class will open up a new memory space, the new data for storage
- Many-to-many, that is: many lists -> many lists, expand the memory of the existing container, put the old data in the specified location, and put the data that needs to be added in front
In addition to calling addedLists when the class is loaded, it will also be called when:
- Class loading, handling methods, properties, protocols
methodizeClass
- Add methods
addMethods
- Add attributes
_class_addProperty
- Add the agreement
class_addProtocol
- Classification loading
attachCategories
3.conclusion
-
After dyLD enters the program and reads the data, the map_Images callback, in _read_images, loads the data into memory.
Load all classes into gDB_objC_realized_classes and allocatedClasses. Remap all classes by readClass 3. Register all SEL into namedSelectors hash table 4. Add all protocols to the PROTOCOL_map Protocol table and remap all protocols 5. Initialize all non-lazily loaded classes and perform ro and RW operations. 6. Traverse the resolvedFutureClasses array and initialize the lazily loaded classes. Handles all categories, including Class and MeteClass.Copy the code
-
ReadClass 1. Check whether the class is loaded late, yes, Date () of the class was read and set rW, ro 2. AddNamedClass (CLS, mangledName, Replacing) 3. AddClassTableEntry (CLS) was inserted into the table
-
RealizeClassWithoutSwift realizes some information related operations of non-lazy loading classes, creates RW structure for classes, initializes the parent class and metaclass, and binds the parent class, metaclass, root class and subclass
-
MethodizeClass writes ro to RW.