Mach-O二进制文件结构及其应用

1. Mach-O 文件结构

Mach-O(Mach Object) 文件格式是 OS X 与 iOS 系统上的可执行文件格式(类似于windows的 PE 文件及 Linux 的 ELF 文件),了解Mach-O文件格式与相关内容是深入研究 xnu 内核的基础。 Mach-O文件可以使用MachOView查看, Mach-O文件主要包括以下几个部分:

  1. Mach-O header:保存了Mach-O文件的基本信息,包括平台,CPU类型,文件类型,Load Commands个数等信息.
  2. Load Commands:文件的逻辑结构和文件在虚拟内存中的布局。可以根据它找到相关的Section 原始数据。mach-o文件由诸多的load command组成,每个load command所代表的是一种数据类型。每种load command都是结构体struct load_command的扩展结构体。
  3. 数据段: Segment Data:每个段由多个节(Section)组成。节是内容分类的最小管理单元。每个节的描述信息是一个称之为:struct section 的结构体。每个节有一个唯一的名称用来标识这个节。
Mach-O文件格式

2. ASLR技术

ASLR,全称是Address Spce Layout Randomization,地址空间布局随机化,是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,增加了攻击者预测目的地址的难度,防止攻击者直接定位代码位置,阻止溢出攻击。这种技术会使得每个程序或者库每次运行加载到内存中时的基地址都不是固定而是随机的,这种机制会增加黑客的破解难度。

iOS中,Mach-O文件 load Commonds 中LC_DYLD_INFO或者LC_DYLD_INFO_ONLY 就是用来记录所有需要进行地址调整的位置。这样当程序被加载到内存时,加载器就会将需要调整的地址分别进行调整处理,以便转化为真实的内存地址。这个过程称之为基地址重定向(rebase)。

3. Mach-O 主要数据结构

3.1 Mach-O header

可以使用otool 命令查看 Mach-O Header信息

➜ otool -h -v QQKSong
QQKSong:
Mach header
      magic  cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64   X86_64        ALL  0x00     EXECUTE    79       9104   NOUNDEFS DYLDLINK TWOLEVEL WEAK_DEFINES BINDS_TO_WEAK PIE MH_HAS_TLV_DESCRIPTORS

Mach-O Header信息定义在<mach-o/loader.h>,分32位和64位

/*
 * The 32-bit mach header appears at the very beginning of the object file for
 * 32-bit architectures.
 */
struct mach_header {
    uint32_t    magic;      /* mach magic number identifier */
    cpu_type_t  cputype;    /* cpu specifier */
    cpu_subtype_t   cpusubtype; /* machine specifier */
    uint32_t    filetype;   /* type of file */
    uint32_t    ncmds;      /* number of load commands */
    uint32_t    sizeofcmds; /* the size of all the load commands */
    uint32_t    flags;      /* flags */
};

/* Constant for the magic field of the mach_header (32-bit architectures) */
#define MH_MAGIC    0xfeedface  /* the mach magic number */
#define MH_CIGAM    0xcefaedfe  /* NXSwapInt(MH_MAGIC) */

/*
 * The 64-bit mach header appears at the very beginning of object files for
 * 64-bit architectures.
 */
struct mach_header_64 {
    uint32_t    magic;      /* mach magic number identifier */
    cpu_type_t  cputype;    /* cpu specifier */
    cpu_subtype_t   cpusubtype; /* machine specifier */
    uint32_t    filetype;   /* type of file */
    uint32_t    ncmds;      /* number of load commands */
    uint32_t    sizeofcmds; /* the size of all the load commands */
    uint32_t    flags;      /* flags */
    uint32_t    reserved;   /* reserved */
};

/* Constant for the magic field of the mach_header_64 (64-bit architectures) */
#define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */
#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */
  • magic: 32位系统为MH_MAGIC,64位系统为MH_MAGIC_64
  • filetype: 包含以下几种格式
#define MH_OBJECT   0x1     /* relocatable object file  编译过程中产生的  obj文件 (gcc -c xxx.c 生成xxx.o文件)*/
#define MH_EXECUTE  0x2     /* demand paged executable file */
#define MH_FVMLIB   0x3     /* fixed VM shared library file */
#define MH_CORE     0x4     /* core file */
#define MH_PRELOAD  0x5     /* preloaded executable file */
#define MH_DYLIB    0x6     /* dynamically bound shared library */
#define MH_DYLINKER 0x7     /* dynamic link editor */
#define MH_BUNDLE   0x8     /* dynamically bound bundle file */
#define MH_DYLIB_STUB   0x9     /* shared library stub for static
                       linking only, no section contents */
#define MH_DSYM     0xa     /* companion file with only debug
                       sections */
#define MH_KEXT_BUNDLE  0xb     /* x86_64 kexts */
#define MH_FILESET  0xc     /* a file composed of other Mach-Os to
                       be run in the same userspace sharing
                       a single linkedit. */
  • ncmds: Load Command的个数
  • sizeofcmds: Load Command的长度
  • flags: dyld加载时需要的标志

3.2 LoadCommands

Load Command告诉操作系统应当如何加载文件中的数据,对系统内核加载器和动态链接器起指导作用。Load Command的定义如下

struct load_command {
    uint32_t cmd;       /* type of load command */
    uint32_t cmdsize;   /* total size of command in bytes */
};

根据cmd类型的不同,使用不同的函数来加载(cmd的值详见<mach-o/loader.h>LC_XXX定义)。以下是常见的command类型

  • LC-SEGMENT,LC-SEGMENT-64: 在内核中由load-segment 函数处理(将segment中的数据加载并映射到进程的内存空间去)
  • LC-LOAD-DYLINKER: 默认的加载器路径,在内核中由load-dylinker 函数处理(调用/usr/lib/dyld程序)
  • LC-UUID: 标识Mach-O文件的ID,也用于崩溃堆栈和符号文件的对应解析. 在内核中由load-uuid 函数处理 (加载128-bit的唯一ID)
  • LC-THREAD: 在内核中由load-thread 函数处理 (开启一个MACH线程,但是不分配栈空间)
  • LC-UNIXTHREAD: 在内核中由load-unixthread 函数处理 (开启一个UNIX posix线程)
  • LC-CODE-SIGNATURE: 在内核中由load-code-signature 函数处理 (进行数字签名)
  • LC-ENCRYPTION-INFO: 在内核中由 set-code-unprotect 函数处理 (加密二进制文件)
  • LC_MAIN:程序的入口。dyld 获取该地址,然后跳转到该处执行。

3.3 Segment Data

  • segment_command_64 的定义
/*
 * The 64-bit segment load command indicates that a part of this file is to be
 * mapped into a 64-bit task's address space.  If the 64-bit segment has
 * sections then section_64 structures directly follow the 64-bit segment
 * command and their size is reflected in cmdsize.
 */
struct segment_command_64 { /* for 64-bit architectures */
    uint32_t    cmd;        /* LC_SEGMENT_64 */
    uint32_t    cmdsize;    /* includes sizeof section_64 structs */
    char        segname[16];    /* segment name */
    uint64_t    vmaddr;     /* memory address of this segment */
    uint64_t    vmsize;     /* memory size of this segment */
    uint64_t    fileoff;    /* file offset of this segment */
    uint64_t    filesize;   /* amount to map from the file */
    vm_prot_t   maxprot;    /* maximum VM protection */
    vm_prot_t   initprot;   /* initial VM protection */
    uint32_t    nsects;     /* number of sections in segment */
    uint32_t    flags;      /* flags */
};
  • section 的定义
struct section_64 { /* for 64-bit architectures */
    char        sectname[16];   /* name of this section */
    char        segname[16];    /* segment this section goes in */
    uint64_t    addr;       /* memory address of this section */
    uint64_t    size;       /* size in bytes of this section */
    uint32_t    offset;     /* file offset of this section */
    uint32_t    align;      /* section alignment (power of 2) */
    uint32_t    reloff;     /* file offset of relocation entries */
    uint32_t    nreloc;     /* number of relocation entries */
    uint32_t    flags;      /* flags (section type and attributes)*/
    uint32_t    reserved1;  /* reserved (for offset or index) */
    uint32_t    reserved2;  /* reserved (for count or sizeof) */
    uint32_t    reserved3;  /* reserved */
};

3.4 常见Segement

_PAGEZERO:空指针陷阱段,映射到虚拟内存空间的第1页,用于捕捉对 的引用。

  • __TEXT :代码段/只读数据段,常见的数据节如下:
__TEXT section 用途
__text 可执行代码区域
__stubs 间接符号存根,跳转到懒加载指针表
__stub_helper 帮助解决懒加载符号加载的辅助函数
__const 常量数据
__objc_methname objc方法名
__cstring 程序中硬编码的ANSI的字符串
__objc_classname objc类名
__objc_methtype objc方法类型
__gcc_except_tab 异常处理相关
__ustring unicode字符串
__unwind_info 异常处理
__eh_frame 异常处理
  • __DATA :读取和写入数据的段
__DATA section 用途
__nl_symbol_ptr 动态符号链接相关,指针数组
__got 全局偏移表, Global Offset Table
__la_symbol_ptr 懒加载指针表,第1次调用时才会绑定值,通过dyld_stub_binder辅助链接
__mod_init_func constructor, 初始化的全局函数地址,会在main之前被调用
__mod_term_func destructor函数
__const const修饰的常量
__cstring 程序中硬编码的ANSI的字符串
__cfstring CF用到的字符串
__objc_classlist objc类列表
__objc_nlclslist 程序中自己实现了+load方法的类列表
__objc_catlist objc category列表
__objc_protolist objc protocol列表
__objc_imageinfo 镜像信息
__objc_const objc的常量
__objc_selrefs objc引用的SEL列表
__objc_protorefs objc引用的protocol列表
__objc_classrefs objc引用的class列表
__objc_superrefs objc父类的引用列表
__objc_ivar objcivar信息
__objc_data class信息
__bss 未初始化的静态变量区
__data 初始化的可变变量

4 使用otool查看Mach-O文件

➜ otool
Usage: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool [-arch arch_type] [-fahlLDtdorSTMRIHGvVcXmqQjCP] [-mcpu=arg] [--version] <object file> ...
    -f print the fat headers
    -a print the archive header
    -h print the mach header
    -l print the load commands
    -L print shared libraries used
    -D print shared library id name
    -t print the text section (disassemble with -v)
    -x print all text sections (disassemble with -v)
    -p <routine name>  start dissassemble from routine name
    -s <segname> <sectname> print contents of section
    -d print the data section
    -o print the Objective-C segment
    -r print the relocation entries
    -S print the table of contents of a library (obsolete)
    -T print the table of contents of a dynamic shared library (obsolete)
    -M print the module table of a dynamic shared library (obsolete)
    -R print the reference table of a dynamic shared library (obsolete)
    -I print the indirect symbol table
    -H print the two-level hints table (obsolete)
    -G print the data in code table
    -v print verbosely (symbolically) when possible
    -V print disassembled operands symbolically
    -c print argument strings of a core file
    -X print no leading addresses or headers
    -m don't use archive(member) syntax
    -B force Thumb disassembly (ARM objects only)
    -q use llvm's disassembler (the default)
    -Q use otool(1)'s disassembler
    -mcpu=arg use `arg' as the cpu for disassembly
    -j print opcode bytes
    -P print the info plist section as strings
    -C print linker optimization hints
    --version print the version of /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool

4.1 查看引用的方法: otool -v -s __DATA __objc_selrefs

➜ otool -v -s __DATA __objc_selrefs QQKSong
QQKSong:
Contents of (__DATA,__objc_selrefs) section
0000000104b5cf28  __TEXT:__objc_methname:stringWithFormat:
0000000104b5cf30  __TEXT:__objc_methname:dataWithBytes:length:
0000000104b5cf38  __TEXT:__objc_methname:encryptData:publicKey:
0000000104b5cf40  __TEXT:__objc_methname:length
0000000104b5cf48  __TEXT:__objc_methname:bytes

4.2 查看所有方法: otool -v -s __TEXT __objc_methname

➜  otool -v -s __TEXT __objc_methname QQKSong
QQKSong:
Contents of (__TEXT,__objc_methname) section
0000000103f31b36  stringWithFormat:
0000000103f31b48  dataWithBytes:length:
0000000103f31b5e  encryptData:publicKey:
0000000103f31b75  length
0000000103f31b7c  bytes
0000000103f31b82  decryptData:publicKey:
0000000103f31b99  base64EncodedStringWithOptions:
0000000103f31bb9  UTF8String

4.3 查看所有 objc信息: otool -ov

➜  otool -ov QQKSong
baseMethods    0x10479d2d8 __OBJC_$_INSTANCE_METHODS_WSGlobalTaskMgr
            entsize 24
            count   14
            name    0x104029b1a stateName:
            types   0x10415ce86 r*24@0:8Q16
            imp     0x101e88b10 -[WSGlobalTaskMgr stateName:]
            name    0x103f31ef9 init
            types   0x1041380d0 @16@0:8
            imp     0x10000d430 -[WSGlobalTaskMgr init]
            name    0x103f5b581 updateStartupStateTimeCostInfo:timeCost:
            types   0x10413912e v32@0:8@16Q24
            imp     0x101e88c20 -[WSGlobalTaskMgr updateStartupStateTimeCostInfo:timeCost:]
            name    0x103f9aa91 runTaskOnState:arg:removeCommonTask:
            types   0x10415ce92 v36@0:8Q16^v24B32
            imp     0x1000cd4e0 -[WSGlobalTaskMgr runTaskOnState:arg:removeCommonTask:]
            name    0x104029afc runTaskOnState:stateName:arg:
            types   0x10415cea4 v40@0:8Q16r*24^v32
            imp     0x1000cd720 -[WSGlobalTaskMgr runTaskOnState:stateName:arg:]
            name    0x104029b25 getSlideIndexForHeader:

通过获取impl后面的值,可以获取到所有实现的方法

5 应用1: 代码瘦身:删除无用代码

利用__DATA __objc_selrefs及所有实现函数的差值,找到未使用的函数

5.1 使用otool -v -s __DATA __objc_selrefs 找到所有项目引用的selector

def ref_selectors(path):
    print 'Get ref selectors ...'
    re_selrefs = re.compile('__TEXT:__objc_methname:(.+)')
    ref_sels = set()
    lines = os.popen('/usr/bin/otool -v -s __DATA __objc_selrefs %s' % path).readlines()
    for line in lines:
        results = re_selrefs.findall(line)
        if results:
            ref_sels.add(results[0])
    return ref_sels

5.2 遍历项目中的所有头文件查找所有Protocol的方法

协议调用的方法不会出现在__DATA __objc_selrefs这个段里面,过滤协议方法采用的策略有:

  1. 找到相应的.h文件,正则匹配文件中包含的协议方法。
def header_protocol_selectors(file_path):
    file_path = file_path.strip()
    if not os.path.isfile(file_path):
        return None
    protocol_sels = set()
    file = open(file_path, 'r')
    is_protocol_area = False
    for line in file.readlines():
        #delete description
        line = re.sub('\".*\"', '', line)
        #delete annotation
        line = re.sub('//.*', '', line)
        #match @protocol
        if re.compile('\s*@protocol\s*\w+').findall(line):
            is_protocol_area = True
        #match @end
        if re.compile('\s*@end').findall(line):
            is_protocol_area = False
        #match sel
        if is_protocol_area and re.compile('\s*[-|+]\s*\(').findall(line):
            sel_content_match_result = None
            if ':' in line:
                #match sel with parameters
                sel_content_match_result = re.compile('\w+\s*:').findall(line)
            else:
                #match sel without parameters
                sel_content_match_result = re.compile('\w+\s*;').findall(line)
            if sel_content_match_result:
                protocol_sels.add(''.join(sel_content_match_result).replace(';', ''))
    file.close()
    return protocol_sels
  1. 可以通过otool -ov的输出结果里面获取Protocol的所有方法,相对比较麻烦,暂未实现,后续实现

5.3 使用otool -ov获取所有实现的方法

所有实现的方法都有如下格式,可通过正则表达式萃取

imp     0x101e88b10 -[WSGlobalTaskMgr stateName:]

代码如下:

def imp_selectors(path):
    print 'Get imp selectors...'
    #return struct: {'setupHeaderShadowView':['-[TTBaseViewController setupHeaderShadowView]']}
    re_sel_imp = re.compile('\s*imp\s*0x\w+ ([+|-]\[.+\s(.+)\])')
    re_properties_start = re.compile('\s*baseProperties 0x\w{9}')
    re_properties_end = re.compile('\w{16} 0x\w{9} _OBJC_CLASS_\$_(.+)')
    re_property = re.compile('\s*name\s*0x\w+ (.+)')
    imp_sels = {}
    is_properties_area = False
    for line in os.popen('/usr/bin/otool -oV %s' % path).xreadlines():
        results = re_sel_imp.findall(line)
        if results:
            (class_sel, sel) = results[0]
            if sel in imp_sels:
                imp_sels[sel].add(class_sel)
            else:
                imp_sels[sel] = set([class_sel])
        else:
            #delete setter and getter methods as ivar assignment will not trigger them
            if re_properties_start.findall(line):
                is_properties_area = True
            if re_properties_end.findall(line):
                is_properties_area = False
            if is_properties_area:
                property_result = re_property.findall(line)
                if property_result:
                    property_name = property_result[0]
                    if property_name and property_name in imp_sels:
                        #properties layout in mach-o is after func imp
                        imp_sels.pop(property_name)
                        setter = 'set' + property_name[0].upper() + property_name[1:] + ':'
                        if setter in imp_sels:
                            imp_sels.pop(setter)
    return imp_sels

5.4 完整代码

#!/usr/bin/python
#encoding=utf8

import os
import re
import sys
import getopt

reserved_prefixs = ["-[","+["]

def verified_app_path(path):
    if path.endswith('.app'):
        appname = path.split('/')[-1].split('.')[0]
        path = os.path.join(path, appname)
        if appname.endswith('-iPad'):
            path = path.replace(appname, appname[:-5])
    if not os.path.isfile(path):
        return None
    if not os.popen('file -b ' + path).read().startswith('Mach-O'):
        return None
    return path


def header_protocol_selectors(file_path):
    file_path = file_path.strip()
    if not os.path.isfile(file_path):
        return None
    protocol_sels = set()
    file = open(file_path, 'r')
    is_protocol_area = False
    for line in file.readlines():
        #delete description
        line = re.sub('\".*\"', '', line)
        #delete annotation
        line = re.sub('//.*', '', line)
        #match @protocol
        if re.compile('\s*@protocol\s*\w+').findall(line):
            is_protocol_area = True
        #match @end
        if re.compile('\s*@end').findall(line):
            is_protocol_area = False
        #match sel
        if is_protocol_area and re.compile('\s*[-|+]\s*\(').findall(line):
            sel_content_match_result = None
            if ':' in line:
                #match sel with parameters
                sel_content_match_result = re.compile('\w+\s*:').findall(line)
            else:
                #match sel without parameters
                sel_content_match_result = re.compile('\w+\s*;').findall(line)
            if sel_content_match_result:
                protocol_sels.add(''.join(sel_content_match_result).replace(';', ''))
    file.close()
    return protocol_sels


def protocol_selectors(path , project_dir):
    print 'Get protocol selectors...'
    header_files = set()
    protocol_sels = set()
    system_base_dir = '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk'
    #get system librareis
    lines = os.popen('otool -L ' + path).readlines()
    for line in lines:
        line = line.strip()
        #delete description
        line = re.sub('\(.*\)', '', line).strip()
        if line.startswith('/System/Library/'):
            library_dir = system_base_dir + '/'.join(line.split('/')[0:-1])
            if os.path.isdir(library_dir):
                header_files = header_files.union(os.popen('find %s -name \"*.h\"' % library_dir).readlines())

    header_files = header_files.union(os.popen('find %s -name \"*.h\"' % project_dir).readlines())
    for header_path in header_files:
        header_protocol_sels = header_protocol_selectors(header_path)
        if header_protocol_sels:
            protocol_sels = protocol_sels.union(header_protocol_sels)
    return protocol_sels

'''
实现的所有方法:/usr/bin/otool -oV 
        baseMethods    0x10446a328 __OBJC_$_CLASS_METHODS_RCTAsyncLocalStorage
            entsize 24
            count   9
            name    0x103f378cf moduleName
            types   0x1041380d0 @16@0:8
            imp     0x1001791d0 +[RCTAsyncLocalStorage moduleName]
            name    0x103f3792e load
            types   0x104138177 v16@0:8
            imp     0x100005ad0 +[RCTAsyncLocalStorage load]
'''
def imp_selectors(path):
    print 'Get imp selectors...'
    #return struct: {'setupHeaderShadowView':['-[TTBaseViewController setupHeaderShadowView]']}
    re_sel_imp = re.compile('\s*imp\s*0x\w+ ([+|-]\[.+\s(.+)\])')
    re_properties_start = re.compile('\s*baseProperties 0x\w{9}')
    re_properties_end = re.compile('\w{16} 0x\w{9} _OBJC_CLASS_\$_(.+)')
    re_property = re.compile('\s*name\s*0x\w+ (.+)')
    imp_sels = {}
    is_properties_area = False
    for line in os.popen('/usr/bin/otool -oV %s' % path).xreadlines():
        results = re_sel_imp.findall(line)
        if results:
            (class_sel, sel) = results[0]
            if sel in imp_sels:
                imp_sels[sel].add(class_sel)
            else:
                imp_sels[sel] = set([class_sel])
        else:
            #delete setter and getter methods as ivar assignment will not trigger them
            if re_properties_start.findall(line):
                is_properties_area = True
            if re_properties_end.findall(line):
                is_properties_area = False
            if is_properties_area:
                property_result = re_property.findall(line)
                if property_result:
                    property_name = property_result[0]
                    if property_name and property_name in imp_sels:
                        #properties layout in mach-o is after func imp
                        imp_sels.pop(property_name)
                        setter = 'set' + property_name[0].upper() + property_name[1:] + ':'
                        if setter in imp_sels:
                            imp_sels.pop(setter)
    return imp_sels

'''
引用的方法: /usr/bin/otool -v -s __DATA __objc_selrefs 
QQKSong:
Contents of (__DATA,__objc_selrefs) section
0000000104b5cf28  __TEXT:__objc_methname:stringWithFormat:
0000000104b5cf30  __TEXT:__objc_methname:dataWithBytes:length:
0000000104b5cf38  __TEXT:__objc_methname:encryptData:publicKey
'''
def ref_selectors(path):
    print 'Get ref selectors ...'
    re_selrefs = re.compile('__TEXT:__objc_methname:(.+)')
    ref_sels = set()
    lines = os.popen('/usr/bin/otool -v -s __DATA __objc_selrefs %s' % path).readlines()
    for line in lines:
        results = re_selrefs.findall(line)
        if results:
            ref_sels.add(results[0])
    return ref_sels


def ignore_selectors(sel):
    if sel == '.cxx_destruct':
        return True
    if sel == '.cxx_construct':
        return True
    if sel == 'load':
        return True
    if sel == 'initialize':
        return True
    return False


def filter_selectors(sels):
    filter_sels = set()
    for sel in sels:
        for prefix in reserved_prefixs:
            if sel.startswith(prefix):    
                filter_sels.add(sel)
    return filter_sels


def unref_selectors(path , project_dir):
    protocol_sels = protocol_selectors(path,project_dir)
    ref_sels = ref_selectors(path)
    if len(ref_sels) == 0:
        exit('Error: ref selectors count null')
    imp_sels = imp_selectors(path)
    if len(imp_sels) == 0:
        exit('Error: imp selectors count null')
    unref_sels = set()
    for sel in imp_sels:
        if ignore_selectors(sel):
            continue
        #protocol sels will not apppear in selrefs section
        if sel not in ref_sels and sel not in protocol_sels:
            unref_sels = unref_sels.union(filter_selectors(imp_sels[sel]))
    return unref_sels


def ignore_class(sel,ignoreClassList):
    for unref_sel in ignoreClassList:
        if sel.find(unref_sel) >= 0:
            return True
    return False
    
    
def parseOpt(argv):
    appFile = ''
    sourceDir = ''
    try:
        opts, args = getopt.getopt(argv,"ha:s:",["app=","sourcedir="])
    except getopt.GetoptError:
        print 'test.py -i <inputfile> -o <outputfile>'
        sys.exit(2)
    for opt, arg in opts:
        if opt == '-h':
            print 'selectorsunref.py -a <app File> -s <source Dir>'
            sys.exit()
        elif opt in ("-a", "--app"):
            appFile = arg
        elif opt in ("-s", "--sourcedir"):
            sourceDir = arg
    return appFile, sourceDir

def main(argv):
    appFile,sourceDir = parseOpt(argv) 
    print 'appFile: ',appFile
    print 'sourceDir:', sourceDir
    
    appFile = verified_app_path(appFile)
    # 判断文件是否存在
    if not appFile:
        exit('Error: invalid app path')

    if not os.path.isdir(sourceDir):
        exit('Error: project path error')
        
    # 移除输出文件
    outputfile = 'selectorunrefs.txt'
    if os.path.exists(outputfile):
        os.remove(outputfile)
    
    # 获取无用方法
    unref_sels = unref_selectors(appFile, sourceDir)
    unref_selsList = list(unref_sels)
    
    # 获取忽略的类
    ignoreClassList = set()
    ignoreDataFile = open("ignore.data")
    for line in ignoreDataFile:
        ignoreClassList.add(line.strip())
    # 忽略hippy相关类
    for unref_sel in unref_selsList:
        if (unref_sel.find('__rct_export') >= 0) :
            beg = unref_sel.find('[')
            end = unref_sel.find(' ')
            ignoreClass = unref_sel[beg + 1:end].strip()
            ignoreClassList.add(ignoreClass)


    f = open(os.path.join(sys.path[0].strip(), outputfile), 'w')
    f.write('忽略的类 begin:\n')
    # 输出忽略的类
    for line in ignoreClassList:
        f.write(line + '\n')
        print(line)
    f.write('忽略的类 end:\n\n\n')
    
    f.write('selectorunrefs count: %d\n' % len(unref_sels))
    
    count = 0
    unref_selsList.sort(cmp=None,key=None,reverse=False)
    for unref_sel in unref_selsList:
        if (unref_sel.find('[WS') >=0 or unref_sel.find('[KS') >=0) \
            and (ignore_class(unref_sel, ignoreClassList) == False):
            f.write(unref_sel + '\n')
            count += 1
           
    f.close()
    print('Done! %d selectors is unreferenced, selectorunref.txt has already stored in script dir.' % count)
    
    

if __name__ == '__main__':
    main(sys.argv[1:])
    

6 应用2: 使用section 端注册函数

目前要在 didFinishLaunchingWithOptions, fireOnWnsLoginSucc,onLogInSucess添加任务,都需要在相应的函数里面添加代码,可扩展性和维护性不高(fireOnWnsLoginSucc,onLogInSucess可以通过注册相应通知来实现非嵌入添加任务,但是通知的方式性能比较差),我们可以利用Clang提供的编译器特性,在编译期将相关函数注册到二进制文件(_Data的相应段),在运行时读取相关配置来执行相关函数
通过该框架,我们可以很方便地添加一个在特定阶段执行的代码

//在application:didFinishLaunchingWithOptions阶段执行的代码
RUN_FUNCTION_ON_STAY(kStartup)
{
    NSLog(@"File: %s,Line: %d",__FILE__,__LINE__);
}

//在main开始阶段执行的代码
RUN_FUNCTION_ON_STAY(kMain)
{
    NSLog(@"File: %s,Line: %d",__FILE__,__LINE__);
}

//在首页渲染完毕后执行的代码
RUN_FUNCTION_ON_STAY(kAppeared)
{
    NSLog(@"File: %s,Line: %d",__FILE__,__LINE__);
}

//在登录完成后执行的代码
RUN_FUNCTION_ON_STAY(kLogin)
{
    NSLog(@"File: %s,Line: %d",__FILE__,__LINE__);
}

//在登录完成后且首页渲染完毕后执行
RUN_FUNCTION_ON_STAY(kLoginEx)
{
    NSLog(@"File: %s,Line: %d",__FILE__,__LINE__);
}
image.png

参考文献

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Macho-O 是什么 Mach-O 是 Mach object 文件格式的缩写,它是一种用于记录可执行文件、对象...
    伶俐ll阅读 2,778评论 0 1
  • Mach-O 类型的文件:是一种用于可执行文件、目标代码、动态库、内核转储的文件格式; 使用工具 MachOVie...
    苏沫离阅读 1,768评论 0 2
  • Mach-O Mach-O是Mach Object文件格式的缩写。它是用于可执行文件,动态库,目标代码的文件格式。...
    Jack_deng阅读 4,346评论 3 2
  • Mach-O Mach-O文件格式是 OS X 与 iOS 系统上的可执行文件格式,类似于windows的 PE ...
    Joolybgo阅读 4,690评论 0 1
  • 1. 概述 在Mac的开发中, 有没有想过当我们点击可执行文件之后,Mac究竟做了什么事情才让我们的程序运行起来?...
    大成小栈阅读 4,513评论 0 7