前言
前面的应用程序加载我们已经探究了从dyld->libSystem->libDispatch->Objc_init
的整体流程。从上面的流程我们知道dyld
主要是链接
了程序需要的镜像文件images
(macho
格式的文件),并将其映射
到了程序
里面,到这一步这些镜像images
还没有加载到内存
里面,接下来我们就来探究这样一个images->内存
的过程。接着上一篇文章也是回归我们熟悉的Objc源码
,从Objc_init
开始继续我们的工作。
预备工作
首先我们看下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();
#if __OBJC2__
cache_t::init();
#endif
_imp_implementationWithBlock_init();
/* ******* 这个通知非常重要 这里的几个参数 非常重要 ******/
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
在Objc_init()
方法里一共调用了8
个方法。我们来捋一遍:
environ_init()
:
这个方法主要是获取运行过程中的一些环境变量,我们可以利用这些环境变量来进行调试和使用。具体的操作请看文章结尾处的 补充内容。
environ_init()
:读取影响运⾏时的环境变量。如果需要,还可以打印环境变量帮助。
tls_init()
:
tls_init()
: 关于线程key的绑定 - ⽐如每线程数据的析构函数 。
static_init()
:
static_init()
:运⾏C++
静态构造函数。在dyld
调⽤我们的静态构造函数之前,libc
会调⽤_objc_init()
,因此我们必须⾃⼰做。
runtime_init()
:
void runtime_init(void)
{
objc::unattachedCategories.init(32);
objc::allocatedClasses.init();
}
点击unattachedCategories
再进去看看:
static UnattachedCategories unattachedCategories;
} // namespace objc
点击allocatedClasses
再进去看看:
namespace objc {
static ExplicitInitDenseSet allocatedClasses;
}
再分别点击这两个方法的init
:
template
class ExplicitInit {
alignas(Type) uint8_t _storage[sizeof(Type)];
public:
template
void init(Ts &&... Args) {
new (_storage) Type(std::forward(Args)...);
}
Type &get() {
return *reinterpret_cast(_storage);
}
};
发现最后都是一张表在初始化,只是暂时不知道储存什么。
runtime_init()
: runtime运⾏时环境初始化,⾥⾯主要是unattachedCategories
、allocatedClasses
等
lock_init()
:
lock_init()
:没有重写,采⽤C++
的特性。
exception_init()
:
我们点击进入看看实现:
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
点击_objc_terminate
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)();
}
}
}
从上面代码我们看到当正常情况只会走__cxa_rethrow();
当出现异常的时候就走下面的(*uncaught_handler)((id)e);
和 (*old_terminate)();
。这里就是一个回调。这个回调就是底层程序在运行的过程中出现了不符合底层运行规矩的情况,这时候就会利用系统提前在底层下的句柄回调到上层,抛出异常的信号。这也就是我们常说的抛出异常
。既然如此我们就可以考虑利用这个地方的这个回调做一些异常拦截的事情,这个我们放在最后的补充部分。
exception_init()
:初始化libobjc
的异常处理系统。
cache_t::init()
:
cache_t::init()
: 缓存条件初始化。
_imp_implementationWithBlock_init()
:
_imp_implementationWithBlock_init()
:启动回调机制。通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib
。
_dyld_objc_notify_register(&map_images, load_images, unmap_image)
:
_dyld_objc_notify_register(&map_images, load_images, unmap_image)
:消息注册为dyld
里的回调准备map_images
、load_images
、unmap_image
等信息。
ps
:在上面的通知注册里携带的&map_images
中&
符号的作用是这个map_images
为指针传递,为了实现map_images
变化能够达到同步。因为这个map_images
太过重要必须保持实时更新。所以采取这种方法。
上一篇文章我们知道objc_init()
方法里进行了dyld
回调通知的注册,并且我们发现它带回了一些东西,那么我们就接着这一步先去看看_dyld_objc_notify_register(&map_images, load_images, unmap_image)
看看他到底带回了些什么,这些东西又是怎么出现的。我们直接查看这个方法里的第一个参数map_images
。
map_images
探索
点击进入map_images
方法
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;
/*
* 省略前面的初始化 以及一些细节处理的代码
*/
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
firstTime = NO;
// Call image load funcs after everything is set up.
for (auto func : loadImageFuncs) {
for (uint32_t i = 0; i < mhCount; i++) {
func(mhdrs[I]);
}
}
}
从上面方法主要在于_read_images()
这个方法的调用去读取images
。
_read_images
方法分析:
这个方法我们可以分为10
个部分去分析。根据内容和注释我们可以根据每个ts.log
来划分。同时我们的目标是要寻找关于类的加载操作,所以不是对类进行操作的步骤我们暂时忽略
• 1: 条件控制进⾏⼀次的加载
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
//省略部分代码
/* **************条件控制进行一次的加载*****************/
if (!doneOnce) {
doneOnce = YES;
launchTime = 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 the app is too old
// (linked before OS X 10.11)
// if (!dyld_program_sdk_at_least(dyld_platform_version_macOS_10_11)) {
// DisableNonpointerIsa = true;
// if (PrintRawIsa) {
// _objc_inform("RAW ISA: disabling non-pointer isa because "
// "the app is too old.");
// }
// }
// 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);
ts.log("IMAGE TIMES: first time tasks");
}
}
这个方法前面都是一些_objc_inform
和环境变量控制。下面这个地方我们需要留意下:initializeTaggedPointerObfuscator();
这个小对象类型的处理和混淆。NXCreateMapTable
是创建了一个表,这张表是一张总表,(可以gdb_objc_realized_classes
跳转去查看下注释):
NXMapTable *NXCreateMapTable(NXMapTablePrototype prototype, unsigned capacity) {
return NXCreateMapTableFromZone(prototype, capacity, malloc_default_zone());
}
NXMapTable *NXCreateMapTableFromZone(NXMapTablePrototype prototype, unsigned capacity, void *z) {
NXMapTable *table = (NXMapTable *)malloc_zone_malloc((malloc_zone_t *)z, sizeof(NXMapTable));
NXMapTablePrototype *proto;
if (! prototypes) prototypes = NXCreateHashTable(protoPrototype, 0, NULL);
if (! prototype.hash || ! prototype.isEqual || ! prototype.free || prototype.style) {
_objc_inform("*** NXCreateMapTable: invalid creation parameters\n");
return NULL;
}
proto = (NXMapTablePrototype *)NXHashGet(prototypes, &prototype);
if (! proto) {
proto = (NXMapTablePrototype *)malloc(sizeof(NXMapTablePrototype));
*proto = prototype;
(void)NXHashInsert(prototypes, proto);
}
table->prototype = proto; table->count = 0;
table->nbBucketsMinusOne = exp2u(log2u(capacity)+1) - 1;
table->buckets = allocBuckets(z, table->nbBucketsMinusOne + 1);
return table;
}
这个表的扩容点也是3/4
。利用一个数学算法来计算当前添加的值x
是否超过了总容量。来判断是否扩容。
• 2: 修复预编译阶段的 @selector
的混乱问题
// Fix up @selector references
static size_t UnfixedSelectors;
{
mutex_locker_t lock(selLock);
for (EACH_HEADER) {
if (hi->hasPreoptimizedSelectors()) 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]);
SEL sel = sel_registerNameNoLock(name, isBundle);
if (sels[i] != sel) {
sels[i] = sel;
}
}
}
}
ts.log("IMAGE TIMES: fix up selector references");
这个方法主要是修复一些在预编译之后@selector的混乱问题,因为可能很多方法名字相同,比如
retain,
名字一样但是
地址不同。所以这个就需要处理,因为对应每个
镜像文件的
位置不同。(获取两个方法的位置不一样一个是
registerNameNoLock(
dyld链接后的)获取的,一个是从
_getObjc2SelectorRefs表里获取的,可以利用跑
objc源码在这个
sels[i] = sel;`地方断点然后利用打印的方式来查看)
• 3: 错误混乱的类处理
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
for (EACH_HEADER) {
if (! mustReadClasses(hi, hasDyldRoots)) {
// Image is sufficiently optimized that we need not call readClass()
continue;
}
classref_t const *classlist = _getObjc2ClassList(hi, &count);
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->hasPreoptimizedClasses();
for (i = 0; i < count; i++) {
Class cls = (Class)classlist[I];
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
if (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.
resolvedFutureClasses = (Class *)
realloc(resolvedFutureClasses,
(resolvedFutureClassCount+1) * sizeof(Class));
resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
}
}
}
ts.log("IMAGE TIMES: discover classes");
这个方法主要是处理一些被处理的类比如被删除的类(因为类的位置发生了变化所以系统会把原来地址的类删除),但是系统没有清理完全,导致了野指针所以需要处理。也叫未来类
。我们来看看这个地方是否是我们要找的关于类加载到内存ro
、rw
的过程。
3.1:readClass
在这个循环比较过程中有一步readClass
。
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
const char *mangledName = cls->nonlazyMangledName();
if (missingWeakSuperclass(cls)) {
// No superclass (probably weak-linked).
// Disavow any knowledge of this subclass.
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) {
if (Class newCls = popFutureNamedClass(mangledName)) {
// This name was previously allocated as a future class.
// Copy objc_class to future class's struct.
// Preserve future's rw data block.
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;
}
这个方法我们看到有关于rw
、ro
的一些处理。那我们我们来探索下是否真的是在这里执行了rw
、ro
的处理呢?首先我们直接在mangledName
后面打印读取的方法和mangledName
如图2:
既然我们可以全部打印那我们是否可以控制打印呢?我们只打印我们自己的类。利用判断来实现。然后跟踪我们自己的类 看他是如何加载进入内存的。如图3:
然后我们在if (mangledName != nullptr) {...}
里添加我们的打印,查看我们自己的类是否进入了下面的if (mangledName != nullptr) {...}
并且进行ro
、rw
的读取。并且在下方的addNamedClass(cls, mangledName, replacing)
和addClassTableEntry(cls)
两个方法加上断点看看是否会进入这两个方法。如图4、5、6、7:
从上面的打印截图我们可以看到并没有进入到我们期待的if
判断语句也没有去进行rw
、ro
的处理。
3.2:addNamedClass()
:
有进入addNamedClass
这个方法,不过这个方法只是把我们的类加到表的一个操作所以可以忽略。
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 be in 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());
}
3.3addClassTableEntry()
:
进入了addClassTableEntry(cls)
方法
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 in the dynamic table already.
auto &set = objc::allocatedClasses.get();
ASSERT(set.find(cls) == set.end());
if (!isKnownClass(cls))
set.insert(cls);
if (addMeta)
addClassTableEntry(cls->ISA(), false);
}
在这个方法我们看到类
和其元类
加载到一个表
,但是到此我们还没看到对这个类有加载到ro
、rw
的操作。
从上面的分析我们可得知类加载到
ro
、rw
的操作并不在readClass
方法里。这个方法只是把类加载到表的操作
• 4:修复重映射⼀些没有被镜像⽂件加载进来的 类
// Fix up remapped classes
// Class list and nonlazy class list remain unremapped.
// Class refs and super refs are remapped for message dispatching.
if (!noClassesRemapped()) {
for (EACH_HEADER) {
Class *classrefs = _getObjc2ClassRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[I]);
}
// fixme why doesn't test future1 catch the absence of this?
classrefs = _getObjc2SuperRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[I]);
}
}
}
ts.log("IMAGE TIMES: remap classes");
• 5: 修复⼀些消息!
// Fix up old objc_msgSend_fixup call sites
for (EACH_HEADER) {
message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
if (count == 0) continue;
if (PrintVtables) {
_objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
"call sites in %s", count, hi->fname());
}
for (i = 0; i < count; i++) {
fixupMessageRef(refs+i);
}
}
ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
• 6: 当我们类⾥⾯有协议的时候 : readProtocol
// Discover protocols. Fix up protocol refs.
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
Class cls = (Class)&OBJC_CLASS_$_Protocol;
ASSERT(cls);
NXMapTable *protocol_map = protocols();
bool isPreoptimized = hi->hasPreoptimizedProtocols();
// Skip reading protocols if this is an image from the shared cache
// and we support roots
// Note, after launch we do need to walk the protocol as the protocol
// in the shared cache is marked with isCanonical() and that may not
// be true if some non-shared cache binary was chosen as the canonical
// definition
if (launchTime && isPreoptimized) {
if (PrintProtocols) {
_objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
hi->fname());
}
continue;
}
bool isBundle = hi->isBundle();
protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
ts.log("IMAGE TIMES: discover protocols");
• 7: 修复没有被加载的协议
// Fix up @protocol references
// Preoptimized images may have the right
// answer already but we don't know for sure.
for (EACH_HEADER) {
// At launch time, we know preoptimized image refs are pointing at the
// shared cache definition of a protocol. We can skip the check on
// launch, but have to visit @protocol refs for shared cache images
// loaded later.
if (launchTime && hi->isPreoptimized())
continue;
protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
for (i = 0; i < count; i++) {
remapProtocolRef(&protolist[I]);
}
}
ts.log("IMAGE TIMES: fix up @protocol references");
• 8: 分类处理
// Discover categories. Only do this after the initial category
// attachment has been done. For categories present at startup,
// discovery is deferred until the first load_images call after
// the call to _dyld_objc_notify_register completes. rdar://problem/53119145
if (didInitialAttachCategories) {
for (EACH_HEADER) {
load_categories_nolock(hi);
}
}
ts.log("IMAGE TIMES: discover categories");
• 9: 类的加载处理
// Category discovery MUST BE Late to avoid potential races
// when other threads call the new category code before
// this thread finishes its fixups.
// +load handled by prepare_load_methods()
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
classref_t const *classlist = hi->nlclslist(&count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
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
}
realizeClassWithoutSwift(cls, nil);
}
}
ts.log("IMAGE TIMES: realize non-lazy classes");
这部分代码我们可以重点分析下,因为刚好是针对类的处理,看是否真的是我们找的对类的加载处理以及ro
、rw
处理。
我们看到这段代码主要是做一个遍历然后调用两个方法addClassTableEntry(cls);
和realizeClassWithoutSwift(cls, nil);
我们前面看过addClassTableEntry(cls);
只是把类添加到表,这里就不看了我们直接看后面这个方法。(考虑到文章的内容过长问题我们利用下一篇文章来分析这里的内容)
• 10 : 没有被处理的类 优化那些被污染的类
// Realize newly-resolved future classes, in case CF manipulates them
if (resolvedFutureClasses) {
for (i = 0; i < resolvedFutureClassCount; i++) {
Class cls = resolvedFutureClasses[I];
if (cls->isSwiftStable()) {
_objc_fatal("Swift class is not allowed to be future");
}
realizeClassWithoutSwift(cls, nil);
cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
}
free(resolvedFutureClasses);
}
ts.log("IMAGE TIMES: realize future classes");
_read_images
方法总结:
• 1: 条件控制进⾏⼀次的加载
• 2: 修复预编译阶段的@selector
的混乱问题
• 3: 错误混乱的类处理
• 4:修复重映射⼀些没有被镜像⽂件加载进来的 类
• 5: 修复⼀些消息!
• 6: 当我们类⾥⾯有协议的时候 :readProtocol
• 7: 修复没有被加载的协议
• 8: 分类处理
• 9: 类的加载处理
• 10 : 没有被处理的类 优化那些被污染的类
补充
1:环境变量
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;
}
}
// 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);
}
}
}
在上面的方法我们看到最后打印这些环境变量是在PrintHelp
和 PrintOptions
条件满足下才能打印那我们直接把打印代码搬出去避免这两个条件的限制然后运行打印看看。(这里是一种方法还有一种打印环境变量的方法就是直接去终端输入命令export OBJC_HELP=1
就能看到下图调试栏同样的信息了)
发现确实打印出了很多环境变量里面就有我们比较熟悉的OBJC_DISABLE_NONPOINTER_ISA
,是否禁止NONPOINTER_ISA
。下面我们来试验一下,把这个环境变量添加到我们的项目里看下打开和关闭的效果:
先设置环境变量OBJC_DISABLE_NONPOINTER_ISA
但是不打开
我们发现打印的isa
确实不是纯的isa
。
下面我们把环境变量OBJC_DISABLE_NONPOINTER_ISA
打开
打开OBJC_DISABLE_NONPOINTER_ISA
后我们发现isa发生了变化,变成了纯isa
了只剩下class
部分的信息了。这就是环境变量的作用。
2:异常拦截
我们看这样一段代码,点击按钮我们看到会崩溃:
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, copy) NSArray *dataArray;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.dataArray = @[@"zy1",@"zy2",@"zy3",@"zy4",@"zy5"];
}
- (IBAction)exceptionAction:(UIButton *)sender {
NSLog(@"%@",self.dataArray[5]);
}
@end
2021-07-29 15:32:39.500204+0800 ZYProjectFourteenth000[60961:3517949] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndexedSubscript:]: index 5 beyond bounds [0 .. 4]'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff20422fba __exceptionPreprocess + 242
1 libobjc.A.dylib 0x00007fff20193ff5 objc_exception_throw + 48
2 CoreFoundation 0x00007fff204a1523 _CFThrowFormattedException + 194
3 CoreFoundation 0x00007fff204449db +[__NSArrayI allocWithZone:] + 0
4 ZYProjectFourteenth000 0x00000001091cd53f -[ViewController exceptionAction:] + 95
5 UIKitCore 0x00007fff246c7937 -[UIApplication sendAction:to:from:forEvent:] + 83
6 UIKitCore 0x00007fff23fe845d -[UIControl sendAction:to:forEvent:] + 223
7 UIKitCore 0x00007fff23fe8780 -[UIControl _sendActionsForEvents:withEvent:] + 332
8 UIKitCore 0x00007fff23fe707f -[UIControl touchesEnded:withEvent:] + 500
9 UIKitCore 0x00007fff24703d01 -[UIWindow _sendTouchesForEvent:] + 1287
10 UIKitCore 0x00007fff24705b8c -[UIWindow sendEvent:] + 4792
11 UIKitCore 0x00007fff246dfc89 -[UIApplication sendEvent:] + 596
12 UIKitCore 0x00007fff247727ba __processEventQueue + 17124
13 UIKitCore 0x00007fff24768560 __eventFetcherSourceCallback + 104
14 CoreFoundation 0x00007fff20390ede __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
15 CoreFoundation 0x00007fff20390dd6 __CFRunLoopDoSource0 + 180
16 CoreFoundation 0x00007fff2039029e __CFRunLoopDoSources0 + 242
17 CoreFoundation 0x00007fff2038a9f7 __CFRunLoopRun + 875
18 CoreFoundation 0x00007fff2038a1a7 CFRunLoopRunSpecific + 567
19 GraphicsServices 0x00007fff2b874d85 GSEventRunModal + 139
20 UIKitCore 0x00007fff246c14df -[UIApplication _run] + 912
21 UIKitCore 0x00007fff246c639c UIApplicationMain + 101
22 ZYProjectFourteenth000 0x00000001091cd7d2 main + 114
23 libdyld.dylib 0x00007fff2025abbd start + 1
)
libc++abi: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndexedSubscript:]: index 5 beyond bounds [0 .. 4]'
terminating with uncaught exception of type NSException
CoreSimulator 757.5 - Device: iPhone 12 (3F4D0D71-98AE-48A7-96B5-720E1FD726F0) - Runtime: iOS 14.5 (18E182) - DeviceType: iPhone 12
(lldb)
我们bt一下看下堆栈:
从上面看到当异常出现的时候就会objc_exception_rethrow
最后进入到objc
的_objc_terminate()
。这里再次验证了上面我们对exception_init()
的分析。
下面我们来拦截下这个异常因为在上面我们分析_objc_terminate()
的时候就知道了他的回调函数是uncaught_handler
。所以我们利用这个函数就能捕获到异常。如下
@interface LGUncaughtExceptionHandle : NSObject
@property (nonatomic) BOOL dismissed;
+ (void)installUncaughtSignalExceptionHandler;
@end
@implementation LGUncaughtExceptionHandle
/// Exception
void LGExceptionHandlers(NSException *exception) {
NSLog(@"%s",__func__);
int32_t exceptionCount = atomic_fetch_add_explicit(&LGUncaughtExceptionCount,1,memory_order_relaxed);
if (exceptionCount > LGUncaughtExceptionMaximum) {
return;
}
// 获取堆栈信息 - model 编程思想
NSArray *callStack = [LGUncaughtExceptionHandle lg_backtrace];
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
[userInfo setObject:exception.name forKey:LGUncaughtExceptionHandlerSignalExceptionName];
[userInfo setObject:exception.reason forKey:LGUncaughtExceptionHandlerSignalExceptionReason];
[userInfo setObject:callStack forKey:LGUncaughtExceptionHandlerAddressesKey];
[userInfo setObject:exception.callStackSymbols forKey:LGUncaughtExceptionHandlerCallStackSymbolsKey];
[userInfo setObject:@"LGException" forKey:LGUncaughtExceptionHandlerFileKey];
[[[LGUncaughtExceptionHandle alloc] init]
performSelectorOnMainThread:@selector(lg_handleException:)
withObject:
[NSException
exceptionWithName:[exception name]
reason:[exception reason]
userInfo:userInfo]
waitUntilDone:YES];
}
+ (void)installUncaughtSignalExceptionHandler{
// uncaught_handler() = fn = LGExceptionHandlers
// objc_setUncaughtExceptionHandler()
NSSetUncaughtExceptionHandler(&LGExceptionHandlers);
}
#import "AppDelegate.h"
#import "LGUncaughtExceptionHandle.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[LGUncaughtExceptionHandle installUncaughtSignalExceptionHandler];
return YES;
}
简单的这样处理下 然后在AppDelegate
里调用下。
效果如下:
完整的demo
我之后再补上来。
这样我们就能捕获到系统抛出的异常然后在我们自己的方法里做一些记录或者上报处理
文章到此结束。下一篇我们继续上面没有分析完的类的加载处理代码段的realizeClassWithoutSwift(cls, nil);
方法进行分析。
遇事不决,可问春风。站在巨人的肩膀上学习,如有疏忽或者错误的地方还请多多指教。谢谢!