前言
在上一篇iOS底层原理之dyld应用程序加载中我们知道了dyld加载完成之后会调用libsystem,然后进入libobjc,调用objc_init,今天让我们研究一下_objc_init和之后的流程。
资料
一、_objc_init
这里面进行了以下操作
- environ_init 环境初始化
- tls_init 线程k的绑定
- static_init 全局静态c++函数的调用
- runtime_init runtime运行时的初始化
- objc::unattachedCategories.init(32);
- objc::allocatedClasses.init();
- exception_init 异常捕获初始化
- cache_t::init 缓存初始化
- _imp_implementationWithBlock_init 启动回调机制
- _dyld_objc_notify_register //重点
代码如下
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();
// 线程k的绑定
tls_init();
// 全局静态c++函数的调用
static_init();
/*
* runtime运行时的初始化
* objc::unattachedCategories.init(32);
* objc::allocatedClasses.init();
*/
runtime_init();
// 异常捕获初始化
exception_init();
#if __OBJC2__
// 缓存初始化
cache_t::init();
#endif
// 启动回调机制
_imp_implementationWithBlock_init();
// 通知注册
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
// map_images()
// load_images()
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
1.environ_init
读取影响运⾏时的环境变量。如果需要,还可以打印环境变量帮助
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;
}
// Turn off autorelease LRU coalescing by default for apps linked against
// older SDKs. LRU coalescing can reorder releases and certain older apps
// are accidentally relying on the ordering.
// rdar://problem/63886091
// if (!dyld_program_sdk_at_least(dyld_fall_2020_os_versions))
// DisableAutoreleaseCoalescingLRU = true;
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;
}
if (0 == strncmp(*p, "OBJC_DEBUG_POOL_DEPTH=", 22)) {
SetPageCountWarning(*p + 22);
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;
}
}
// 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);
// _objc_inform("%s is set", opt->env);
// }
// if (!os_feature_enabled_simple(objc4, preoptimizedCaches, true)) {
// DisablePreoptCaches = true;
// }
// 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);
}
}
}
- 通过设置OBJC_HELP和OBJC_PRINT_OPTIONS在控制台可以打印出环境变量
设置步骤: target -> Edit scheme -> Run -> Arguments -> Environments Variables
- 通过终端命令打印
export OBJC_HELP=1
2.static_init
- 静态初始化
- 运行C++静态构造函数。
- libc在dyld调用静态构造函数之前调用了_objc_init(),
- 所以我们必须自己动手。
看里面调用的两个方法好像是对macho里面的header和segement做的处理,没有想明白
/***********************************************************************
* 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]();
}
auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
UnsignedInitializer init(offsets[i]);
init();
}
}
// getLibobjcInitializers
struct UnsignedInitializer {
private:
uintptr_t storage;
public:
UnsignedInitializer(uint32_t offset) {
storage = (uintptr_t)&_mh_dylib_header + offset;
}
void operator () () const {
using Initializer = void(*)();
Initializer init =
ptrauth_sign_unauthenticated((Initializer)storage,
ptrauth_key_function_pointer, 0);
init();
}
};
uint32_t *getLibobjcInitializerOffsets(const headerType *mhdr, size_t *outCount) {
unsigned long byteCount = 0;
uint32_t *offsets = (uint32_t *)getsectiondata(mhdr, "__TEXT", "__objc_init_offs", &byteCount);
if (outCount) *outCount = byteCount / sizeof(uint32_t);
return offsets;
}
3.runtime_init
void runtime_init(void)
{
objc::unattachedCategories.init(32);
objc::allocatedClasses.init();
}
4.exception_init
- exception_init
- 初始化 libobjc 的异常处理系统。
- 由 map_images() 调用。
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
- 自定义std::终止处理程序。
- 未捕获的异常回调实现为std::terminate处理程序。
- 1.检查是否存在活动异常
- 2.如果是,检查是否是Objective-C异常
- 3.如果是这样,用对象调用我们注册的回调。
- 4.最后,调用前面的终止处理程序。
/***********************************************************************
* _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)();
}
}
}
5._imp_implementationWithBlock_init
启用回调机制
/// Initialize the trampoline machinery. Normally this does nothing, as
/// everything is initialized lazily, but for certain processes we eagerly load
/// the trampolines dylib.
void
_imp_implementationWithBlock_init(void)
{
#if TARGET_OS_OSX
// Eagerly load libobjc-trampolines.dylib in certain processes. Some
// programs (most notably QtWebEngineProcess used by older versions of
// embedded Chromium) enable a highly restrictive sandbox profile which
// blocks access to that dylib. If anything calls
// imp_implementationWithBlock (as AppKit has started doing) then we'll
// crash trying to load it. Loading it here sets it up before the sandbox
// profile is enabled and blocks it.
//
// This fixes EA Origin (rdar://problem/50813789)
// and Steam (rdar://problem/55286131)
if (__progname &&
(strcmp(__progname, "QtWebEngineProcess") == 0 ||
strcmp(__progname, "Steam Helper") == 0)) {
Trampolines.Initialize();
}
#endif
}
6._dyld_objc_notify_register
//
//注意:只供objc运行时使用
//在映射、取消映射和初始化objc映像时要调用的寄存器处理程序。
//Dyld将使用包含objc image info部分的image数组回调“mapped”函数。
//那些是dylibs的image的ref计数将被自动缓冲,因此objc将不再需要这样做
//对它们调用dlopen(),以防止它们被卸载。在调用\u dyld \u objc \u notify \u register()期间,
//dyld将使用已加载的objc image调用“mapped”函数。在以后的dlopen()调用中,
//dyld还将调用“mapped”函数。Dyld将在调用Dyld时调用“init”函数
//image中的初始值设定项。这是objc调用该映像中任何+load方法的时候。
//
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped);
· map_images
- 处理由dyld映射的给定image。
- 在获取ABI特定锁后调用ABI不可知代码。
- 锁定:写入锁定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);
}
· map_images_nolock
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
static bool firstTime = YES;
header_info *hList[mhCount];
uint32_t hCount;
size_t selrefCount = 0;
// 如有必要,执行第一次初始化。
// 此函数在普通库初始化程序之前调用。
// fixme 延迟初始化,直到找到使用 objc 的image?
if (firstTime) {
preopt_init();
}
if (PrintImages) {
_objc_inform("IMAGES: processing %u newly-mapped images...\n", mhCount);
}
// 查找所有带有 Objective-C 元数据的image。
hCount = 0;
// Count classes. Size various table based on the total.
int totalClasses = 0;
int unoptimizedTotalClasses = 0;
{
uint32_t i = mhCount;
while (i--) {
const headerType *mhdr = (const headerType *)mhdrs[i];
auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
if (!hi) {
// no objc data in this entry
continue;
}
if (mhdr->filetype == MH_EXECUTE) {
// Size some data structures based on main executable's size
#if __OBJC2__
// If dyld3 optimized the main executable, then there shouldn't
// be any selrefs needed in the dynamic map so we can just init
// to a 0 sized map
if ( !hi->hasPreoptimizedSelectors() ) {
size_t count;
_getObjc2SelectorRefs(hi, &count);
selrefCount += count;
_getObjc2MessageRefs(hi, &count);
selrefCount += count;
}
#else
_getObjcSelectorRefs(hi, &selrefCount);
#endif
#if SUPPORT_GC_COMPAT
// Halt if this is a GC app.
if (shouldRejectGCApp(hi)) {
_objc_fatal_with_reason
(OBJC_EXIT_REASON_GC_NOT_SUPPORTED,
OS_REASON_FLAG_CONSISTENT_FAILURE,
"Objective-C garbage collection "
"is no longer supported.");
}
#endif
}
hList[hCount++] = hi;
if (PrintImages) {
_objc_inform("IMAGES: loading image for %s%s%s%s%s\n",
hi->fname(),
mhdr->filetype == MH_BUNDLE ? " (bundle)" : "",
hi->info()->isReplacement() ? " (replacement)" : "",
hi->info()->hasCategoryClassProperties() ? " (has class properties)" : "",
hi->info()->optimizedByDyld()?" (preoptimized)":"");
}
}
}
// Perform one-time runtime initialization that must be deferred until
// the executable itself is found. This needs to be done before
// further initialization.
// (The executable may not be present in this infoList if the
// executable does not contain Objective-C code but Objective-C
// is dynamically loaded later.
if (firstTime) {
sel_init(selrefCount);
arr_init();
#if SUPPORT_GC_COMPAT
// Reject any GC images linked to the main executable.
// We already rejected the app itself above.
// Images loaded after launch will be rejected by dyld.
for (uint32_t i = 0; i < hCount; i++) {
auto hi = hList[i];
auto mh = hi->mhdr();
if (mh->filetype != MH_EXECUTE && shouldRejectGCImage(mh)) {
_objc_fatal_with_reason
(OBJC_EXIT_REASON_GC_NOT_SUPPORTED,
OS_REASON_FLAG_CONSISTENT_FAILURE,
"%s requires Objective-C garbage collection "
"which is no longer supported.", hi->fname());
}
}
#endif
#if TARGET_OS_OSX
// Disable +initialize fork safety if the app is too old (< 10.13).
// Disable +initialize fork safety if the app has a
// __DATA,__objc_fork_ok section.
// if (!dyld_program_sdk_at_least(dyld_platform_version_macOS_10_13)) {
// DisableInitializeForkSafety = true;
// if (PrintInitializing) {
// _objc_inform("INITIALIZE: disabling +initialize fork "
// "safety enforcement because the app is "
// "too old.)");
// }
// }
for (uint32_t i = 0; i < hCount; i++) {
auto hi = hList[i];
auto mh = hi->mhdr();
if (mh->filetype != MH_EXECUTE) continue;
unsigned long size;
if (getsectiondata(hi->mhdr(), "__DATA", "__objc_fork_ok", &size)) {
DisableInitializeForkSafety = true;
if (PrintInitializing) {
_objc_inform("INITIALIZE: disabling +initialize fork "
"safety enforcement because the app has "
"a __DATA,__objc_fork_ok section");
}
}
break; // assume only one MH_EXECUTE image
}
#endif
}
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
firstTime = NO;
// 一切设置完毕后调用image加载函数。
for (auto func : loadImageFuncs) {
for (uint32_t i = 0; i < mhCount; i++) {
func(mhdrs[i]);
}
}
}
二、_read_images
/***********************************************************************
* _read_images
* 对链接中的标头执行初始处理
* 以 headerList 开头的列表。
*
* 调用者:map_images_nolock
*
* Locking:map_images获取的runtimeLock
**********************************************************************/
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) {
// 省略代码 300+的代码太长了
//条件控制第一次加载
if (!doneOnce) {...}
// 修复预编译阶段@selector的混乱问题
static size_t UnfixedSelectors; {...}
//错误混乱的类处理
bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
//修复、重新映射没有被镜像文件加载进来的类
if (!noClassesRemapped()) {...}
// 修复旧的 objc_msgSend 修复调用站点
for (EACH_HEADER) {
message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
... }
// 发现协议
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
... }
//修复没有加载的协议
for (EACH_HEADER) {
if (launchTime && hi->isPreoptimized())
... }
//分类的加载
if (didInitialAttachCategories) {
for (EACH_HEADER) {
load_categories_nolock(hi);
}
}
// 实现非懒加载类(用于+load 方法和静态实例)
for (EACH_HEADER) {
classref_t const *classlist = hi->nlclslist(&count);
... }
// 实现没有被处理的类,优化被入侵的类
if (resolvedFutureClasses) {...}
...
}
1.readClass
/***********************************************************************
* readClass
* 读取由编译器编写的类和元类。
* 返回新的类指针。 这可能是:
* - cls
* - nil(cls 缺少弱链接超类)
* - 别的东西(这个class的空间被未来的class保留了)
*
* 请注意,此功能执行的所有工作均由
* mustReadClasses()。 不要在不更新该功能的情况下更改该功能。
*
* Locking:由map_images或objc_readClassPair获取的runtimeLock
**********************************************************************/
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
const char *mangledName = cls->nonlazyMangledName();
//容错处理(情况一):return nil
if (missingWeakSuperclass(cls)) {
// 没有父类 (可能是弱链接).
// 否认对这个子类的任何了解。
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING class '%s' with "
"missing weak-linked superclass",
cls->nameForLogging());
}
addRemappedClass(cls, nil);
cls->setSuperclass(nil);
return nil;
}
cls->fixupBackwardDeployingStableSwift();
Class replacing = nil;
if (mangledName != nullptr) {
//通常情况下并不会在此处进行rw与ro的处理
if (Class newCls = popFutureNamedClass(mangledName)) {
if (newCls->isAnySwift()) {
_objc_fatal("Can't complete future class request for '%s' "
"because the real class is too big.",
cls->nameForLogging());
}
class_rw_t *rw = newCls->data();
const class_ro_t *old_ro = rw->ro();
memcpy(newCls, cls, sizeof(objc_class));
// Manually set address-discriminated ptrauthed fields
// so that newCls gets the correct signatures.
newCls->setSuperclass(cls->getSuperclass());
newCls->initIsa(cls->getIsa());
rw->set_ro((class_ro_t *)newCls->data());
newCls->setData(rw);
freeIfMutable((char *)old_ro->getName());
free((void *)old_ro);
addRemappedClass(cls, newCls);
replacing = cls;
cls = newCls;
}
}
if (headerIsPreoptimized && !replacing) {
// class list built in shared cache
// fixme strict assert doesn't work because of duplicates
// ASSERT(cls == getClass(name));
ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
} else {
if (mangledName) { //some Swift generic classes can lazily generate their names
addNamedClass(cls, mangledName, replacing);
} else {
Class meta = cls->ISA();
const class_ro_t *metaRO = meta->bits.safe_ro();
ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
}
addClassTableEntry(cls);
}
// for future reference: shared cache never contains MH_BUNDLEs
if (headerIsBundle) {
cls->data()->flags |= RO_FROM_BUNDLE;
cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
}
return cls;
}
2.addNamedClass
/***********************************************************************
* addNamedClass
* 将 name => cls 添加到命名的非元类映射。
* 警告重复的类名并保留旧的映射。
* 锁定:runtimeLock 必须由调用者持有
**********************************************************************/
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 使用名称查找。
// 名称查找未找到的类必须在
// 二级元->非元表。
addNonMetaClass(cls);
} else {
NXMapInsert(gdb_objc_realized_classes, name, cls);
}
ASSERT(!(cls->data()->flags & RO_META));
}
3.addClassTableEntry(关键函数)
将类添加到表里面,如果addMeta为真,并且将当前类的元类也添加到所有的表中
/***********************************************************************
* addClassTableEntry
* 将一个类添加到所有类的表中。 如果 addMeta 为真,
* 也会自动添加类的元类。
* 锁定:runtimeLock 必须由调用者持有。
**********************************************************************/
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
runtimeLock.assertLocked();
// 该类允许通过共享缓存或数据段成为已知类,但不允许已经在动态表中。
auto &set = objc::allocatedClasses.get();
ASSERT(set.find(cls) == set.end());
if (!isKnownClass(cls))
set.insert(cls);
if (addMeta)
//将元类添加到所有表中
addClassTableEntry(cls->ISA(), false);
}
4.调试read_images
readClass通过探索源码可以知道,这是个读取类的方法的函数,那么在此函数里面添加打印事件
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
const char *mangledName = cls->nonlazyMangledName();
const char *LGPersonName = "LGPerson";
if (strcmp(mangledName, LGPersonName) == 0) {
// 普通写得类 他是如何
printf("%s -: 要研究的: - %s\n",__func__,mangledName);
}
...
}
调试结果
readClass -: 要研究的: - LGPerson