环境说明
:objc-818.2源码
、MacOS 11.2.1
系统,Xcode 12.4
,调试可以参考objc-runtime源码编译,一步步来即可,感觉区别于objc-787.1
的部分代码就是注销了的-。-。
上一篇文章APP的加载与dyld主要是研究app启动前到main
方法调用都做了写什么,其中设计到跨库的调用了libobjc
库中的runtime-new.mm
文件中的_objc_init
方法:
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();
runtime_init();
exception_init();
cache_init();
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
在_objc_init
方法中,在调用注册notify
通知前,做了很多出事话的操作,对比上一章所叙述,可以知道这些初始化也是在加载app时就开始做了,那么到底出初始了哪些东西,就在这一章研究下。
1、_objc_init函数分析
一、environ_init()
:
/***********************************************************************
* environ_init
* Read environment variables that affect the runtime.
* Also print environment variable help, if requested.
**********************************************************************/
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;
}
}
PrintHelp = true;
PrintOptions = 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);
}
}
}
通过注释和方法名就大致可以猜出是初始化了环境和读取影响runtime
的环境参数的,并且还可以打印一些环境变量。真正打印的部分在最后if
判断中的for
循环代码中,但是由于条件限制并不会打印出来,所以这里调试一下,将PrintHelp
和PrintOptions
直接设置为true
,并运行,直接打印:

GDB
时期就存在的,那就是Malloc
和NSZombiesEnabled
用来分析崩溃EXC_BAD_ACCESS
时用到的。那怎么设置这边参数就知道了,在Product
——>Scheme
——>Edit Scheme...
——>Run(Debug)
——>Arguments
——>Environment Variables
项下添加参数:
Malloc
将会记录申请过的内存临时变量不销毁,NSZombieEnable
将允许空指针变量不被销毁,所以这两个变量不能长时间打开,内存扛不住。还有许多其他的环境参数如:1、
DYLD_PRINT_STATISTICS
:设置
DYLD_PRINT_STATISTICS
为YES
,控制台就会打印 app
的加载时长,包括整体加载时长和动态库加载时长,即main
函数之前的启动时间(查看pre-main
耗时),可以通过设置了解其耗时部分,并对其进行启动优化。2、
OBJC_DISABLE_NONPOINTER_ISA
:杜绝生成相应的
nonpointer isa
具体参考isa与对象、类的关系,生成的都是普通的isa
。3、
NSDoubleLocalizedStrings
:项目做国际化本地化(Localized)的时候是一个挺耗时的工作,想要检测国际化翻译好的语言文字UI会变成什么样子,可以指定这个启动项。可以设置
NSDoubleLocalizedStrings
为YES。4、
NSShowNonLocalizedStrings
:在完成国际化的时候,偶尔会有一些字符串没有做本地化,这时就可以设置
NSShowNonLocalizedStrings
为YES
,所有没有被本地化的字符串全都会变成大写。5、
OBJC_PRINT_LOAD_METHODS
:打印
Class
及Category
的+ (void)load
方法的调用信息。
二、tls_init()
:
void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
_objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}
TLS
:Thread Local Storage
,是一种通过key-value
形式将线程存储起来的,在iOS中每个线程都拥有自己的TLS
,负责保存本线程的一些变量, 且TLS
无需锁保护。
这里初始化的就是当前执行_objc_init
的线程TLS
,也就是将执行_objc_init
线程与他的存储地址相关联。
三、static_init()
:
/***********************************************************************
* 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();
}
}
根据注释描述可以知道,这个是运行C++
静态构造方法,libc
调用_objc_init
在dyld
调用我们的静态构造函数之前,因此我们需要自己来调用一遍。
四、runtime_init()
:
void runtime_init(void)
{
objc::unattachedCategories.init(32);
objc::allocatedClasses.init();
}
从方法名字就可以知道,这是一个运行时
的初始化方法,包含了未依赖的分类初始化
、已经申请内存类的初始化
五、exception_init()
:
/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
这个初始化,从命名来看就知道是异常的初始化,注释也说明了,这个是初始化的libobjc
的异常处理系统,将会在map_images()
方法调用。
这里在set_terminate
方法中传入的是_objc_terminate
方法,全局搜索一下:
/***********************************************************************
* _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)();
}
}
}
通过注释可以知道,这个方法就是自定义的std::terminate handler
,异常处理操作流程:
1、检测是否存在一个当前活跃的异常;
2、如果活跃检测是否是一个Objc-C
的异常,因为可能还有C/C++
等等;
3、如果是的,将调用Foundation
库的异常处理;
4、最终,调用就的处理,也就是C/C++
的异常处理。
六、cache_t::init()
:
void cache_t::init()
{
#if HAVE_TASK_RESTARTABLE_RANGES
mach_msg_type_number_t count = 0;
kern_return_t kr;
while (objc_restartableRanges[count].location) {
count++;
}
kr = task_restartable_ranges_register(mach_task_self(),
objc_restartableRanges, count);
if (kr == KERN_SUCCESS) return;
_objc_fatal("task_restartable_ranges_register failed (result 0x%x: %s)",
kr, mach_error_string(kr));
#endif // HAVE_TASK_RESTARTABLE_RANGES
}
缓存的初始化。
七、_imp_implementationWithBlock_init()
:
_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
}
imp
的函数实现初始化,可以知道这里是懒加载
形式实现的objc-trampolines
库的初始化。
八、_dyld_objc_notify_register(&map_images, load_images, unmap_image)
:
这个方法是无法在objc
中找到源码,根据11、APP的加载与dyld中的研究可以推测,是在dyld
中实现,在dyld
中找到源码:
//
// 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);
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);
}
分别找到的是头文件声明和cpp
文件的实现,根据注释得到的信息:
1、这个主要是为objc runtime
用的
2、注册操作会在objc镜像
被映射、解除映射和初始化时被调用。
3、dyld
会以一组包含objc-image-info
的信息段的镜像为参数回调映射方法。
4、后面就是一些回调方法调用时机的解释。
这里对应了有三个参数:
mapped
、init
、umapped
对应传入的分别是&map_images
、load_image
、unmap_image
在方法实现中,调用了dyld::registerObjCNotifiers
方法:
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
// record functions to call
sNotifyObjCMapped = mapped;
sNotifyObjCInit = init;
sNotifyObjCUnmapped = unmapped;
// call 'mapped' function with all images mapped so far
try {
notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
}
catch (const char* msg) {
// ignore request to abort during registration
}
// <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)
for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
ImageLoader* image = *it;
if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
//调用sNotifyObjCInit
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
}
}
}
可以知道对应赋值的是sNotifyObjCMapped
、sNotifyObjCInit
、sNotifyObjCUnmapped
。
在上一篇文章11、APP的加载与dyld中分析到的是sNotifyObjCInit
函数指针的调用流程,是通过notifySingle
方法中调用的registerObjCNotifiers
,最后才调用sNotifyObjCInit
函数指针。
再分析一下sNotifyObjCMapped
方法的调用,全局搜索:
static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image_state_change_handler onlyHandler, bool preflightOnly, bool onlyObjCMappedNotification)
{
std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(state, sBatchHandlers);
if ( (handlers != NULL) || ((state == dyld_image_state_bound) && (sNotifyObjCMapped != NULL)) ) {
// don't use a vector because it will use malloc/free and we want notifcation to be low cost
allImagesLock();
dyld_image_info infos[allImagesCount()+1];
ImageLoader* images[allImagesCount()+1];
ImageLoader** end = images;
for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
dyld_image_states imageState = (*it)->getState();
if ( (imageState == state) || (orLater && (imageState > state)) )
*end++ = *it;
}
if ( sBundleBeingLoaded != NULL ) {
dyld_image_states imageState = sBundleBeingLoaded->getState();
if ( (imageState == state) || (orLater && (imageState > state)) )
*end++ = sBundleBeingLoaded;
}
const char* dontLoadReason = NULL;
uint32_t imageCount = (uint32_t)(end-images);
if ( imageCount != 0 ) {
// sort bottom up
qsort(images, imageCount, sizeof(ImageLoader*), &imageSorter);
const mach_header* mhs[imageCount];
const char* paths[imageCount];
uint32_t bulkNotifyImageCount = 0;
// build info array
for (unsigned int i=0; i < imageCount; ++i) {
dyld_image_info* p = &infos[i];
ImageLoader* image = images[i];
//dyld::log(" state=%d, name=%s\n", state, image->getPath());
p->imageLoadAddress = image->machHeader();
p->imageFilePath = image->getRealPath();
p->imageFileModDate = image->lastModified();
// get these registered with the kernel as early as possible
if ( state == dyld_image_state_dependents_mapped)
notifyKernel(*image, true);
// special case for add_image hook
if ( state == dyld_image_state_bound ) {
if ( notifyAddImageCallbacks(image) ) {
// Add this to the list of images to bulk notify
mhs[bulkNotifyImageCount] = infos[i].imageLoadAddress;
paths[bulkNotifyImageCount] = infos[i].imageFilePath;
++bulkNotifyImageCount;
}
}
}
if ( (state == dyld_image_state_bound) && !sAddBulkLoadImageCallbacks.empty() && (bulkNotifyImageCount != 0) ) {
for (LoadImageBulkCallback func : sAddBulkLoadImageCallbacks) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_FUNC_FOR_ADD_IMAGE, (uint64_t)mhs[0], (uint64_t)func, 0);
(*func)(bulkNotifyImageCount, mhs, paths);
}
}
}
#if SUPPORT_ACCELERATE_TABLES
if ( sAllCacheImagesProxy != NULL ) {
unsigned cacheCount = sAllCacheImagesProxy->appendImagesToNotify(state, orLater, &infos[imageCount]);
// support _dyld_register_func_for_add_image()
if ( state == dyld_image_state_bound ) {
for (ImageCallback callback : sAddImageCallbacks) {
for (unsigned i=0; i < cacheCount; ++i) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_FUNC_FOR_ADD_IMAGE, (uint64_t)infos[imageCount+i].imageLoadAddress, (uint64_t)(*callback), 0);
(*callback)(infos[imageCount+i].imageLoadAddress, sSharedCacheLoadInfo.slide);
}
}
for (LoadImageCallback func : sAddLoadImageCallbacks) {
for (unsigned i=0; i < cacheCount; ++i) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_FUNC_FOR_ADD_IMAGE, (uint64_t)infos[imageCount+i].imageLoadAddress, (uint64_t)(*func), 0);
(*func)(infos[imageCount+i].imageLoadAddress, infos[imageCount+i].imageFilePath, false);
}
}
if ( !sAddBulkLoadImageCallbacks.empty() ) {
const mach_header* bulk_mhs[cacheCount];
const char* bulk_paths[cacheCount];
for (int i=0; i < cacheCount; ++i) {
bulk_mhs[i] = infos[imageCount+i].imageLoadAddress;
bulk_paths[i] = infos[imageCount+i].imageFilePath;
}
for (LoadImageBulkCallback func : sAddBulkLoadImageCallbacks) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_FUNC_FOR_ADD_IMAGE, (uint64_t)bulk_mhs[0], (uint64_t)func, 0);
(*func)(cacheCount, bulk_mhs, bulk_paths);
}
}
}
imageCount += cacheCount;
}
#endif
if ( imageCount != 0 ) {
if ( !onlyObjCMappedNotification ) {
if ( onlyHandler != NULL ) {
const char* result = NULL;
if ( result == NULL ) {
result = (*onlyHandler)(state, imageCount, infos);
}
if ( (result != NULL) && (state == dyld_image_state_dependents_mapped) ) {
//fprintf(stderr, " images rejected by handler=%p\n", onlyHandler);
// make copy of thrown string so that later catch clauses can free it
dontLoadReason = strdup(result);
}
}
else {
// call each handler with whole array
if ( handlers != NULL ) {
for (std::vector<dyld_image_state_change_handler>::iterator it = handlers->begin(); it != handlers->end(); ++it) {
const char* result = (*it)(state, imageCount, infos);
if ( (result != NULL) && (state == dyld_image_state_dependents_mapped) ) {
//fprintf(stderr, " images rejected by handler=%p\n", *it);
// make copy of thrown string so that later catch clauses can free it
dontLoadReason = strdup(result);
break;
}
}
}
}
}
// tell objc about new images
if ( (onlyHandler == NULL) && ((state == dyld_image_state_bound) || (orLater && (dyld_image_state_bound > state))) && (sNotifyObjCMapped != NULL) ) {
const char* paths[imageCount];
const mach_header* mhs[imageCount];
unsigned objcImageCount = 0;
for (int i=0; i < imageCount; ++i) {
ImageLoader* image = findImageByMachHeader(infos[i].imageLoadAddress);
bool hasObjC = false;
if ( image != NULL ) {
if ( image->objCMappedNotified() )
continue;
hasObjC = image->notifyObjC();
}
#if SUPPORT_ACCELERATE_TABLES
else if ( sAllCacheImagesProxy != NULL ) {
const mach_header* mh;
const char* path;
unsigned index;
if ( sAllCacheImagesProxy->addressInCache(infos[i].imageLoadAddress, &mh, &path, &index) ) {
hasObjC = (mh->flags & MH_HAS_OBJC);
}
}
#endif
if ( hasObjC ) {
paths[objcImageCount] = infos[i].imageFilePath;
mhs[objcImageCount] = infos[i].imageLoadAddress;
++objcImageCount;
if ( image != NULL )
image->setObjCMappedNotified();
}
}
if ( objcImageCount != 0 ) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_MAP, 0, 0, 0);
uint64_t t0 = mach_absolute_time();
//调用sNotifyObjCMapped
(*sNotifyObjCMapped)(objcImageCount, paths, mhs);
uint64_t t1 = mach_absolute_time();
ImageLoader::fgTotalObjCSetupTime += (t1-t0);
}
}
}
allImagesUnlock();
if ( dontLoadReason != NULL )
throw dontLoadReason;
if ( !preflightOnly && (state == dyld_image_state_dependents_mapped) ) {
const struct mach_header* loadAddresses[imageCount];
const char* loadPaths[imageCount];
for(uint32_t i = 0; i<imageCount; ++i) {
loadAddresses[i] = infos[i].imageLoadAddress;
loadPaths[i] = infos[i].imageFilePath;
}
notifyMonitoringDyld(false, imageCount, loadAddresses, loadPaths);
}
}
}
可以知道是在notifyBatchPartial
方法中调用的sNotifyObjCMapped
,对照registerObjCNotifiers
方法,可以知道与objc
关联的流程是先对镜像进行映射,然后在进行初始化。
总结一下:
1、dyld
流程中在通过调用libobjc
库中的 _objc_init
方法中的_dyld_objc_notify_register
,最终调用dyld::registerObjCNotifiers
方法。
2、在调用_dyld_objc_notify_register
前,_objc_init
方法实现了多个初始化方法,分别初始化了:
1.环境
;
2.线程缓存
;
3.静态构造方法
;
4.类和分类
;
5.OC异常处理
;
6.方法缓存
;
7.objc-trampolines库
。
3、registerObjCNotifiers
方法中先绑定了sNotifyObjCMapped
、sNotifyObjCInit
、sNotifyObjCUnmapped
三个函数指针,然后就通过调用notifyBatchPartial
调用了sNotifyObjCMapped
镜像映射方法。
4、registerObjCNotifiers
最后才调用了sNotifyObjCInit
方法,进行镜像的初始化。
网友评论