🌚

Kam's Online Notebook


基于 Mach-O 的内存管理语义分析

在使用 Objective-C 编写程序时,经常会因为 mistyping 使用内存管理语义修饰符 assign 去修饰对象类型 property,为后续 bad access 或其它运行时异常埋下祸根。

为了避免此类问题,需要通过一些手段检测出异常内存管理修饰符:

  • 可以编写 script 去分析每个 property 声明,但这种方案很复杂(类型识别、宏展开等),准确率随工具的复杂度提高可能会下降;
  • clang plugin 分析是个更好的选择,但这种方式对无源码的 lib 无效。因为我们也需要了解、排除引入的二进制所蕴含的野指针风险,起码 crash 的时候知道哪个 lib 是潜在的 trigger;
  • 最后就是基于对 Objective-C ABI 的理解,简单地通过分析程序的二进制文件,就能够实现异常的属性检测。

Objective-C 类在编译之后,会针对 property 产生 attribute string,这些字符串可以指示出该 property 的原子性、内存管理语义等特征。通过分析二进制所有类的属性特征,可以快速准确地找出所有的异常属性。

举例来说,为类 KTClass 作出如下定义:

@interface KTClass : NSObject
@property (assign) int iVar;
@property (assign) NSString *aStr;
@property (strong) NSString *sStrr;
@property (copy) NSString *cStr;
@property (weak) NSString *wStr;
@end 

上述的五个属性的内存管理语义修饰符分别为 assign、assign、strong、copy、weak。编译器在编译后会为这五个属性生成对应的特征字符串,可以用过 Objective-C 运行时函数 property_getAttrubutes 获得,分别为:

Ti,V_iVar
T@"NSString",V_aStr
T@"NSString",&,V_sStr
T@"NSString",C,V_cStr
T@"NSString",W,V_wStr

通过测试及研究可以发现:

  1. 当属性是对象类型时,「T」字符后会跟着一个「@」字符,而对于一般的基础数据类型,则没有此字符;
  2. 当修饰符为 strong、copy 以及 weak 的时候,都有特殊的字符「&」、「C」或者「W」来标识,而 assign 没有。

那么利用规则:「T」字符后跟着“@”字符且在最后的逗号前遇不到「&」、「C」、「W」的属性,判断为异常属性声明。

有了上述的规则,那么就可以应用到所有类的属性判定上。具体到遍历项目所有的类,需要用到 dyld 的 API,通过 _dyld_get_image_header 函数获得对应的二进制的文件头,然后再通过 getsectiondata 函数获得 __DATA 中的 __objc_classlist 段,这里返回的是编译期生成的所有类列表,遍历,再通过 class_copyPropertyList 获取到类的所有属性列表,遍历所有属性,应用上述所提到的规则。具体可以看下 KTPropertyCheck 的实现。

本案的检测方式在实践中成功定位项目中潜在的或偶现的内存隐患,一定程度上提高了项目质量。上面所提及的核心是利用编译代码后的二进制产物进行分析,除了可以在程序运行期间使用 Objective-C 运行时 API 获得该特征字符串,也可以静态地通过解析二进制文件获得。

EOF

— May 18, 2021