iOS开发之runtime(7):日志打印系统分析

logo

本系列博客是本人的源码阅读笔记,如果有 iOS 开发者在看 runtime 的,欢迎大家多多交流。为了方便讨论,本人新建了一个微信群(iOS技术讨论群),想要加入的,请添加本人微信:zhujinhui207407,【加我前请备注:ios 】,本人博客http://www.kyson.cn 也在不停的更新中,欢迎一起讨论

本文完整版详见笔者小专栏:https://xiaozhuanlan.com/runtime

背景

在前面的文章中,笔者有做过如下打印信息:

if (0 == strncmp(subcls->nameForLogging(), "Person", 5)) {
    printf("subcls:%s,superclass:%s hasCustomAllocZone:%d\n",subcls->nameForLogging(),supercls->nameForLogging(),subcls->ISA()->hasCustomAWZ());
}

用于更好的观察代码的属性,有了这些信息的提示,我们能更好的了解runtime的原理。那么runtime本身有没有类似的打印日志信息用于调试或者观察呢,答案是肯定的,笔者暂时给其起个名字叫“日志打印系统”。这个名字不是笔者随便提出的,而是经过对其代码分析后得出的。因为这套系统是有着较为清晰的设计的,接下来笔者带大家来了解一下。

入口

这里先给大家普及一个知识,函数

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_2_images, load_images, unmap_image);
}

是runtime启动后执行的第一个函数。关于这个知识点,这里笔者先简单提一下,具体的分析会在后面的文章中提出。void _objc_init(void)函数位于文件objc_os.mm中,而这个函数中的这句代码:

environ_init();

即是对log系统的初始化,我们看看里面的代码。

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;
        }
    }

    // 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);
        }
    }
}

对以上代码做个解释:

issetugid
该方法在苹果API Reference中有介绍:issetugid,大概意思是判断当前App的uid或者gid有没有发生变化。这里返回的是NO,继续看后面的代码。

issetugid

_NSGetEnviron
获取Xcode中的环境变量,类似的函数还有:

extern char ***_NSGetArgv(void);
extern int *_NSGetArgc(void);
extern char **_NSGetProgname(void);

这几个函数笔者就不多做介绍了,这里告诉大家如何在Xcode中设置参数,使得_NSGetEnviron能读取到。
1.选择Edit Scheme

Edit Scheme

2.在Arguments中的Environment Variables中添加相应环境变量即可
添加环境变量

接下来的代码都是对环境变量进行读取以及和Settings里的变量进行比对了:

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;
    }
}

Settings即是本地的变量文件,我们点击进入看一下:

const option_t Settings[] = {
#define OPTION(var, env, help) option_t{&var, #env, help, strlen(#env)}, 
#include "objc-env.h"
#undef OPTION
};

可以发现,其环境变量都是在objc-env.h中进行设置的,而该文件的内容如下:

OPTION( PrintImages,              OBJC_PRINT_IMAGES,               "log image and library names as they are loaded")
OPTION( PrintImageTimes,          OBJC_PRINT_IMAGE_TIMES,          "measure duration of image loading steps")
OPTION( PrintLoading,             OBJC_PRINT_LOAD_METHODS,         "log calls to class and category +load methods")
OPTION( PrintInitializing,        OBJC_PRINT_INITIALIZE_METHODS,   "log calls to class +initialize methods")
OPTION( PrintResolving,           OBJC_PRINT_RESOLVED_METHODS,     "log methods created by +resolveClassMethod: and +resolveInstanceMethod:")
OPTION( PrintConnecting,          OBJC_PRINT_CLASS_SETUP,          "log progress of class and category setup")
OPTION( PrintProtocols,           OBJC_PRINT_PROTOCOL_SETUP,       "log progress of protocol setup")
OPTION( PrintIvars,               OBJC_PRINT_IVAR_SETUP,           "log processing of non-fragile ivars")
OPTION( PrintVtables,             OBJC_PRINT_VTABLE_SETUP,         "log processing of class vtables")
OPTION( PrintVtableImages,        OBJC_PRINT_VTABLE_IMAGES,        "print vtable images showing overridden methods")
OPTION( PrintCaches,              OBJC_PRINT_CACHE_SETUP,          "log processing of method caches")
OPTION( PrintFuture,              OBJC_PRINT_FUTURE_CLASSES,       "log use of future classes for toll-free bridging")
OPTION( PrintPreopt,              OBJC_PRINT_PREOPTIMIZATION,      "log preoptimization courtesy of dyld shared cache")
OPTION( PrintCxxCtors,            OBJC_PRINT_CXX_CTORS,            "log calls to C++ ctors and dtors for instance variables")
OPTION( PrintExceptions,          OBJC_PRINT_EXCEPTIONS,           "log exception handling")
OPTION( PrintExceptionThrow,      OBJC_PRINT_EXCEPTION_THROW,      "log backtrace of every objc_exception_throw()")
OPTION( PrintAltHandlers,         OBJC_PRINT_ALT_HANDLERS,         "log processing of exception alt handlers")
OPTION( PrintReplacedMethods,     OBJC_PRINT_REPLACED_METHODS,     "log methods replaced by category implementations")
OPTION( PrintDeprecation,         OBJC_PRINT_DEPRECATION_WARNINGS, "warn about calls to deprecated runtime functions")
OPTION( PrintPoolHiwat,           OBJC_PRINT_POOL_HIGHWATER,       "log high-water marks for autorelease pools")
OPTION( PrintCustomRR,            OBJC_PRINT_CUSTOM_RR,            "log classes with un-optimized custom retain/release methods")
OPTION( PrintCustomAWZ,           OBJC_PRINT_CUSTOM_AWZ,           "log classes with un-optimized custom allocWithZone methods")
OPTION( PrintRawIsa,              OBJC_PRINT_RAW_ISA,              "log classes that require raw pointer isa fields")

OPTION( DebugUnload,              OBJC_DEBUG_UNLOAD,               "warn about poorly-behaving bundles when unloaded")
OPTION( DebugFragileSuperclasses, OBJC_DEBUG_FRAGILE_SUPERCLASSES, "warn about subclasses that may have been broken by subsequent changes to superclasses")
OPTION( DebugNilSync,             OBJC_DEBUG_NIL_SYNC,             "warn about @synchronized(nil), which does no synchronization")
OPTION( DebugNonFragileIvars,     OBJC_DEBUG_NONFRAGILE_IVARS,     "capriciously rearrange non-fragile ivars")
OPTION( DebugAltHandlers,         OBJC_DEBUG_ALT_HANDLERS,         "record more info about bad alt handler use")
OPTION( DebugMissingPools,        OBJC_DEBUG_MISSING_POOLS,        "warn about autorelease with no pool in place, which may be a leak")
OPTION( DebugPoolAllocation,      OBJC_DEBUG_POOL_ALLOCATION,      "halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools")
OPTION( DebugDuplicateClasses,    OBJC_DEBUG_DUPLICATE_CLASSES,    "halt when multiple classes with the same name are present")
OPTION( DebugDontCrash,           OBJC_DEBUG_DONT_CRASH,           "halt the process by exiting instead of crashing")

OPTION( DisableVtables,           OBJC_DISABLE_VTABLES,            "disable vtable dispatch")
OPTION( DisablePreopt,            OBJC_DISABLE_PREOPTIMIZATION,    "disable preoptimization courtesy of dyld shared cache")
OPTION( DisableTaggedPointers,    OBJC_DISABLE_TAGGED_POINTERS,    "disable tagged pointer optimization of NSNumber et al.") 
OPTION( DisableNonpointerIsa,     OBJC_DISABLE_NONPOINTER_ISA,     "disable non-pointer isa fields")

设置里面的任何一个变量为YES,即可观察对应的代码。下面笔者举个例子,查看awz的设置过程:

OPTION( PrintCustomAWZ,           OBJC_PRINT_CUSTOM_AWZ,           "log classes with un-optimized custom allocWithZone methods")
awz设置

运行项目可以看到xcode中有如下日志:


日志

全局搜索CUSTOM AWZ:可以看到日志打印函数在文件objc-runtime-new.mm中:

void 
objc_class::printCustomAWZ(bool inherited)
{
    assert(PrintCustomAWZ);
    assert(hasCustomAWZ());
    _objc_inform("CUSTOM AWZ:  %s%s%s", nameForLogging(), 
                 isMetaClass() ? " (meta)" : "", 
                 inherited ? " (inherited)" : "");
}

打断点查看其调用栈可以发现,其被

objc_class::setInitialized()

调用:


awz调用栈

有了这些日志,我们阅读runtime源代码就更方便啦。

总结

本文介绍了runtime中的日志系统,希望对大家有所帮助。


本文完整版详见笔者小专栏:https://xiaozhuanlan.com/runtime


广告

我的首款个人开发的APP壁纸宝贝上线了,欢迎大家下载。

壁纸宝贝

你可能感兴趣的:(iOS开发之runtime(7):日志打印系统分析)