1. Mach-O 文件结构
Mach-O(Mach Object) 文件格式是 OS X 与 iOS 系统上的可执行文件格式(类似于windows的 PE 文件及 Linux 的 ELF 文件),了解Mach-O文件格式与相关内容是深入研究 xnu 内核的基础。 Mach-O文件可以使用MachOView查看, Mach-O文件主要包括以下几个部分:
-
Mach-O header:保存了Mach-O文件的基本信息,包括平台,CPU类型,文件类型,Load Commands个数等信息. -
Load Commands:文件的逻辑结构和文件在虚拟内存中的布局。可以根据它找到相关的Section 原始数据。mach-o文件由诸多的load command组成,每个load command所代表的是一种数据类型。每种load command都是结构体struct load_command的扩展结构体。 -
数据段: Segment Data:每个段由多个节(Section)组成。节是内容分类的最小管理单元。每个节的描述信息是一个称之为:struct section的结构体。每个节有一个唯一的名称用来标识这个节。

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这个段里面,过滤协议方法采用的策略有:
- 找到相应的.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
- 可以通过
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__);
}
